1
# Copyright (C) 2007 by Jelmer Vernooij <jelmer@samba.org>
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Bazaar command-line subcommands."""
19
from __future__ import absolute_import
21
from ...commands import (
25
from ...errors import (
32
from ...option import (
35
from ...trace import (
39
from ...i18n import gettext
42
def finish_rebase(state, wt, replace_map, replayer):
47
# Start executing plan from current Branch.last_revision()
48
rebase(wt.branch.repository, replace_map, replayer)
49
except ConflictsInTree:
50
raise BzrCommandError(gettext(
51
"A conflict occurred replaying a commit."
52
" Resolve the conflict and run 'brz rebase-continue' or "
53
"run 'brz rebase-abort'."))
58
class cmd_rebase(Command):
61
Rebasing is the process of taking a branch and modifying the history so
62
that it appears to start from a different point. This can be useful
63
to clean up the history before submitting your changes. The tree at the
64
end of the process will be the same as if you had merged the other branch,
65
but the history will be different.
67
The command takes the location of another branch on to which the branch in
68
the specified directory (by default, the current working directory)
69
will be rebased. If a branch is not specified then the parent branch
70
is used, and this is usually the desired result.
72
The first step identifies the revisions that are in the current branch that
73
are not in the parent branch. The current branch is then set to be at the
74
same revision as the target branch, and each revision is replayed on top
75
of the branch. At the end of the process it will appear as though your
76
current branch was branched off the current last revision of the target.
78
Each revision that is replayed may cause conflicts in the tree. If this
79
happens the command will stop and allow you to fix them up. Resolve the
80
commits as you would for a merge, and then run 'brz resolve' to marked
81
them as resolved. Once you have resolved all the conflicts you should
82
run 'brz rebase-continue' to continue the rebase operation.
84
If conflicts are encountered and you decide that you do not wish to continue
85
you can run 'brz rebase-abort'.
87
The '--onto' option allows you to specify a different revision in the
88
target branch to start at when replaying the revisions. This means that
89
you can change the point at which the current branch will appear to be
90
branched from when the operation completes.
92
takes_args = ['upstream_location?']
94
'revision', 'merge-type', 'verbose',
97
help="Show what would be done, but don't actually do anything."),
99
'always-rebase-merges',
100
help="Don't skip revisions that merge already present revisions."),
103
help="Rebase pending merges onto local branch."),
105
'onto', help='Different revision to replay onto.',
110
help="Branch to replay onto, rather than the one containing the working directory.",
115
def run(self, upstream_location=None, onto=None, revision=None,
116
merge_type=None, verbose=False, dry_run=False,
117
always_rebase_merges=False, pending_merges=False,
119
from ...branch import Branch
120
from ...revisionspec import RevisionSpec
121
from ...workingtree import WorkingTree
122
from .rebase import (
123
generate_simple_plan,
126
WorkingTreeRevisionRewriter,
127
regenerate_default_revid,
130
if revision is not None and pending_merges:
131
raise BzrCommandError(gettext(
132
"--revision and --pending-merges are mutually exclusive"))
134
wt = WorkingTree.open_containing(directory)[0]
137
state = RebaseState1(wt)
138
if upstream_location is None:
140
upstream_location = directory
142
upstream_location = wt.branch.get_parent()
143
if upstream_location is None:
144
raise BzrCommandError(gettext("No upstream branch specified."))
145
note(gettext("Rebasing on %s"), upstream_location)
146
upstream = Branch.open_containing(upstream_location)[0]
147
upstream_repository = upstream.repository
148
upstream_revision = upstream.last_revision()
149
# Abort if there already is a plan file
151
raise BzrCommandError(gettext(
152
"A rebase operation was interrupted. "
153
"Continue using 'brz rebase-continue' or abort using 'brz "
158
if revision is not None:
159
if len(revision) == 1:
160
if revision[0] is not None:
161
stop_revid = revision[0].as_revision_id(wt.branch)
162
elif len(revision) == 2:
163
if revision[0] is not None:
164
start_revid = revision[0].as_revision_id(wt.branch)
165
if revision[1] is not None:
166
stop_revid = revision[1].as_revision_id(wt.branch)
168
raise BzrCommandError(gettext(
169
"--revision takes only one or two arguments"))
172
wt_parents = wt.get_parent_ids()
173
if len(wt_parents) in (0, 1):
174
raise BzrCommandError(gettext("No pending merges present."))
175
elif len(wt_parents) > 2:
176
raise BzrCommandError(gettext(
177
"Rebasing more than one pending merge not supported"))
178
stop_revid = wt_parents[1]
179
assert stop_revid is not None, "stop revid invalid"
181
# Check for changes in the working tree.
182
if (not pending_merges and
183
wt.basis_tree().changes_from(wt).has_changed()):
184
raise UncommittedChanges(wt)
186
# Pull required revisions
187
wt.branch.repository.fetch(upstream_repository, upstream_revision)
189
onto = upstream.last_revision()
191
rev_spec = RevisionSpec.from_string(onto)
192
onto = rev_spec.as_revision_id(upstream)
194
wt.branch.repository.fetch(upstream_repository, revision_id=onto)
196
if stop_revid is None:
197
stop_revid = wt.branch.last_revision()
198
repo_graph = wt.branch.repository.get_graph()
199
our_new, onto_unique = repo_graph.find_difference(stop_revid, onto)
201
if start_revid is None:
203
self.outf.write(gettext("No revisions to rebase.\n"))
206
self.outf.write(gettext(
207
"Base branch is descendant of current "
208
"branch. Pulling instead.\n"))
210
wt.pull(upstream, stop_revision=onto)
212
# else: include extra revisions needed to make start_revid mean
216
replace_map = generate_simple_plan(
217
our_new, start_revid, stop_revid, onto, repo_graph,
218
lambda revid, ps: regenerate_default_revid(
219
wt.branch.repository, revid),
220
not always_rebase_merges)
222
if verbose or dry_run:
223
todo = list(rebase_todo(wt.branch.repository, replace_map))
224
note(gettext('%d revisions will be rebased:') % len(todo))
230
state.write_plan(replace_map)
232
replayer = WorkingTreeRevisionRewriter(wt, state, merge_type=merge_type)
234
finish_rebase(state, wt, replace_map, replayer)
239
class cmd_rebase_abort(Command):
240
"""Abort an interrupted rebase."""
246
help="Branch to replay onto, rather than the one containing the working directory.",
250
def run(self, directory="."):
251
from .rebase import (
255
from ...workingtree import WorkingTree
256
wt = WorkingTree.open_containing(directory)[0]
259
state = RebaseState1(wt)
260
# Read plan file and set last revision
262
last_rev_info = state.read_plan()[0]
264
raise BzrCommandError("No rebase to abort")
265
complete_revert(wt, [last_rev_info[1]])
271
class cmd_rebase_continue(Command):
272
"""Continue an interrupted rebase after resolving conflicts."""
274
'merge-type', Option(
277
help="Branch to replay onto, rather than the one containing the working directory.",
281
def run(self, merge_type=None, directory="."):
282
from .rebase import (
284
WorkingTreeRevisionRewriter,
286
from ...workingtree import WorkingTree
287
wt = WorkingTree.open_containing(directory)[0]
290
state = RebaseState1(wt)
291
replayer = WorkingTreeRevisionRewriter(wt, state, merge_type=merge_type)
292
# Abort if there are any conflicts
293
if len(wt.conflicts()) != 0:
294
raise BzrCommandError(gettext(
295
"There are still conflicts present. "
296
"Resolve the conflicts and then run "
297
"'brz resolve' and try again."))
300
replace_map = state.read_plan()[1]
302
raise BzrCommandError(gettext("No rebase to continue"))
303
oldrevid = state.read_active_revid()
304
if oldrevid is not None:
305
oldrev = wt.branch.repository.get_revision(oldrevid)
306
replayer.commit_rebase(oldrev, replace_map[oldrevid][0])
307
finish_rebase(state, wt, replace_map, replayer)
312
class cmd_rebase_todo(Command):
313
"""Print list of revisions that still need to be replayed as part of the
314
current rebase operation.
322
help="Branch to replay onto, rather than the one containing the working directory.",
325
def run(self, directory="."):
326
from .rebase import (
330
from ...workingtree import WorkingTree
331
wt = WorkingTree.open_containing(directory)[0]
333
state = RebaseState1(wt)
335
replace_map = state.read_plan()[1]
337
raise BzrCommandError(gettext("No rebase in progress"))
338
currentrevid = state.read_active_revid()
339
if currentrevid is not None:
340
note(gettext("Currently replaying: %s") % currentrevid)
341
for revid in rebase_todo(wt.branch.repository, replace_map):
342
note(gettext("{0} -> {1}").format(revid, replace_map[revid][0]))
345
class cmd_replay(Command):
346
"""Replay commits from another branch on top of this one.
351
'revision', 'merge-type',
355
help="Branch to replay onto, rather than the one containing the working directory.",
357
takes_args = ['location']
360
def run(self, location, revision=None, merge_type=None, directory="."):
361
from ...branch import Branch
362
from ...workingtree import WorkingTree
364
from .rebase import (
366
regenerate_default_revid,
367
WorkingTreeRevisionRewriter,
370
from_branch = Branch.open_containing(location)[0]
372
if revision is not None:
373
if len(revision) == 1:
374
if revision[0] is not None:
375
todo = [revision[0].as_revision_id(from_branch)]
376
elif len(revision) == 2:
377
from_revno, from_revid = revision[0].in_history(from_branch)
378
to_revno, to_revid = revision[1].in_history(from_branch)
380
to_revno = from_branch.revno()
382
for revno in range(from_revno, to_revno + 1):
383
todo.append(from_branch.get_rev_id(revno))
385
raise BzrCommandError(gettext(
386
"--revision takes only one or two arguments"))
388
raise BzrCommandError(gettext("--revision is mandatory"))
390
wt = WorkingTree.open(directory)
393
state = RebaseState1(wt)
394
replayer = WorkingTreeRevisionRewriter(wt, state, merge_type=merge_type)
395
pb = ui.ui_factory.nested_progress_bar()
398
pb.update(gettext("replaying commits"), todo.index(revid), len(todo))
399
wt.branch.repository.fetch(from_branch.repository, revid)
400
newrevid = regenerate_default_revid(wt.branch.repository, revid)
401
replayer(revid, newrevid, [wt.last_revision()])
408
class cmd_pseudonyms(Command):
409
"""Show a list of 'pseudonym' revisions.
411
Pseudonym revisions are revisions that are roughly the same revision,
412
usually because they were converted from the same revision in a
413
foreign version control system.
416
takes_args = ['repository?']
419
def run(self, repository=None):
420
from ...controldir import ControlDir
421
dir, _ = ControlDir.open_containing(repository)
422
r = dir.find_repository()
423
from .pseudonyms import find_pseudonyms
424
for pseudonyms in find_pseudonyms(r, r.all_revision_ids()):
425
self.outf.write(", ".join(pseudonyms) + "\n")
428
class cmd_rebase_foreign(Command):
429
"""Rebase revisions based on a branch created with a different import tool.
431
This will change the identity of revisions whose parents
432
were mapped from revisions in the other version control system.
434
You are recommended to run "brz check" in the local repository
435
after running this command.
437
takes_args = ['new_base?']
440
Option("idmap-file", help="Write map with old and new revision ids.",
445
help="Branch to replay onto, rather than the one containing the working directory.",
449
def run(self, new_base=None, verbose=False, idmap_file=None, directory="."):
453
from ...branch import Branch
454
from ...workingtree import WorkingTree
455
from .pseudonyms import (
457
generate_rebase_map_from_pseudonyms,
460
from .upgrade import (
461
create_deterministic_revid,
464
from ...foreign import (
465
update_workingtree_fileids,
469
wt_to = WorkingTree.open(directory)
470
branch_to = wt_to.branch
471
except NoWorkingTree:
473
branch_to = Branch.open(directory)
475
stored_loc = branch_to.get_parent()
477
if stored_loc is None:
478
raise BzrCommandError(gettext(
479
"No pull location known or specified."))
481
display_url = urlutils.unescape_for_display(
482
stored_loc, self.outf.encoding)
483
self.outf.write(gettext("Using saved location: %s\n") % display_url)
484
new_base = Branch.open(stored_loc)
486
new_base = Branch.open(new_base)
488
branch_to.repository.fetch(
489
new_base.repository, revision_id=branch_to.last_revision())
491
pseudonyms = pseudonyms_as_dict(find_pseudonyms(
492
branch_to.repository, branch_to.repository.all_revision_ids()))
494
def generate_rebase_map(revision_id):
495
return generate_rebase_map_from_pseudonyms(
496
pseudonyms, branch_to.repository.get_ancestry(revision_id),
497
branch_to.repository.get_ancestry(new_base.last_revision()))
498
def determine_new_revid(old_revid, new_parents):
499
return create_deterministic_revid(old_revid, new_parents)
500
branch_to.lock_write()
502
graph = branch_to.repository.get_graph()
503
renames = upgrade_branch(
504
branch_to, generate_rebase_map,
505
determine_new_revid, allow_changes=True,
507
if wt_to is not None:
508
basis_tree = wt_to.basis_tree()
509
basis_tree.lock_read()
511
update_workingtree_fileids(wt_to, basis_tree)
518
note(gettext("Nothing to do."))
520
if idmap_file is not None:
521
f = open(idmap_file, 'w')
523
for oldid, newid in renames.iteritems():
524
f.write("%s\t%s\n" % (oldid, newid))
528
if wt_to is not None:
529
wt_to.set_last_revision(branch_to.last_revision())