/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/plugins/rewrite/commands.py

[merge] robertc's integration, updated tests to check for retcode=3

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007 by Jelmer Vernooij <jelmer@samba.org>
2
 
#
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.
7
 
#
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.
12
 
#
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
16
 
 
17
 
"""Bazaar command-line subcommands."""
18
 
 
19
 
from __future__ import absolute_import
20
 
 
21
 
from ...commands import (
22
 
    Command,
23
 
    display_command,
24
 
    )
25
 
from ...errors import (
26
 
    BzrCommandError,
27
 
    ConflictsInTree,
28
 
    NoSuchFile,
29
 
    NoWorkingTree,
30
 
    UncommittedChanges,
31
 
    )
32
 
from ...option import (
33
 
    Option,
34
 
    )
35
 
from ...trace import (
36
 
    note,
37
 
    )
38
 
 
39
 
from ...i18n import gettext
40
 
 
41
 
 
42
 
def finish_rebase(state, wt, replace_map, replayer):
43
 
    from .rebase import (
44
 
        rebase,
45
 
        )
46
 
    try:
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'."))
54
 
    # Remove plan file
55
 
    state.remove_plan()
56
 
 
57
 
 
58
 
class cmd_rebase(Command):
59
 
    """Re-base a branch.
60
 
 
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.
66
 
 
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.
71
 
 
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.
77
 
 
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.
83
 
 
84
 
    If conflicts are encountered and you decide that you do not wish to continue
85
 
    you can run 'brz rebase-abort'.
86
 
 
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.
91
 
    """
92
 
    takes_args = ['upstream_location?']
93
 
    takes_options = [
94
 
        'revision', 'merge-type', 'verbose',
95
 
        Option(
96
 
            'dry-run',
97
 
            help="Show what would be done, but don't actually do anything."),
98
 
        Option(
99
 
            'always-rebase-merges',
100
 
            help="Don't skip revisions that merge already present revisions."),
101
 
        Option(
102
 
            'pending-merges',
103
 
            help="Rebase pending merges onto local branch."),
104
 
        Option(
105
 
            'onto', help='Different revision to replay onto.',
106
 
            type=str),
107
 
        Option(
108
 
            'directory',
109
 
            short_name='d',
110
 
            help="Branch to replay onto, rather than the one containing the working directory.",
111
 
            type=str)
112
 
        ]
113
 
 
114
 
    @display_command
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,
118
 
            directory="."):
119
 
        from ...branch import Branch
120
 
        from ...revisionspec import RevisionSpec
121
 
        from ...workingtree import WorkingTree
122
 
        from .rebase import (
123
 
            generate_simple_plan,
124
 
            rebase,
125
 
            RebaseState1,
126
 
            WorkingTreeRevisionRewriter,
127
 
            regenerate_default_revid,
128
 
            rebase_todo,
129
 
            )
130
 
        if revision is not None and pending_merges:
131
 
            raise BzrCommandError(gettext(
132
 
                "--revision and --pending-merges are mutually exclusive"))
133
 
 
134
 
        wt = WorkingTree.open_containing(directory)[0]
135
 
        wt.lock_write()
136
 
        try:
137
 
            state = RebaseState1(wt)
138
 
            if upstream_location is None:
139
 
                if pending_merges:
140
 
                    upstream_location = directory
141
 
                else:
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
150
 
            if state.has_plan():
151
 
                raise BzrCommandError(gettext(
152
 
                    "A rebase operation was interrupted. "
153
 
                    "Continue using 'brz rebase-continue' or abort using 'brz "
154
 
                    "rebase-abort'"))
155
 
 
156
 
            start_revid = None
157
 
            stop_revid = None
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)
167
 
                else:
168
 
                    raise BzrCommandError(gettext(
169
 
                        "--revision takes only one or two arguments"))
170
 
 
171
 
            if pending_merges:
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"
180
 
 
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)
185
 
 
186
 
            # Pull required revisions
187
 
            wt.branch.repository.fetch(upstream_repository, upstream_revision)
188
 
            if onto is None:
189
 
                onto = upstream.last_revision()
190
 
            else:
191
 
                rev_spec = RevisionSpec.from_string(onto)
192
 
                onto = rev_spec.as_revision_id(upstream)
193
 
 
194
 
            wt.branch.repository.fetch(upstream_repository, revision_id=onto)
195
 
 
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)
200
 
 
201
 
            if start_revid is None:
202
 
                if not onto_unique:
203
 
                    self.outf.write(gettext("No revisions to rebase.\n"))
204
 
                    return
205
 
                if not our_new:
206
 
                    self.outf.write(gettext(
207
 
                        "Base branch is descendant of current "
208
 
                        "branch. Pulling instead.\n"))
209
 
                    if not dry_run:
210
 
                        wt.pull(upstream, stop_revision=onto)
211
 
                    return
212
 
            # else: include extra revisions needed to make start_revid mean
213
 
            # something.
214
 
 
215
 
            # Create plan
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)
221
 
 
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))
225
 
                for revid in todo:
226
 
                    note("%s" % revid)
227
 
 
228
 
            if not dry_run:
229
 
                # Write plan file
230
 
                state.write_plan(replace_map)
231
 
 
232
 
                replayer = WorkingTreeRevisionRewriter(wt, state, merge_type=merge_type)
233
 
 
234
 
                finish_rebase(state, wt, replace_map, replayer)
235
 
        finally:
236
 
            wt.unlock()
237
 
 
238
 
 
239
 
class cmd_rebase_abort(Command):
240
 
    """Abort an interrupted rebase."""
241
 
 
242
 
    takes_options = [
243
 
        Option(
244
 
            'directory',
245
 
            short_name='d',
246
 
            help="Branch to replay onto, rather than the one containing the working directory.",
247
 
            type=str)]
248
 
 
249
 
    @display_command
250
 
    def run(self, directory="."):
251
 
        from .rebase import (
252
 
            RebaseState1,
253
 
            complete_revert,
254
 
            )
255
 
        from ...workingtree import WorkingTree
256
 
        wt = WorkingTree.open_containing(directory)[0]
257
 
        wt.lock_write()
258
 
        try:
259
 
            state = RebaseState1(wt)
260
 
            # Read plan file and set last revision
261
 
            try:
262
 
                last_rev_info = state.read_plan()[0]
263
 
            except NoSuchFile:
264
 
                raise BzrCommandError("No rebase to abort")
265
 
            complete_revert(wt, [last_rev_info[1]])
266
 
            state.remove_plan()
267
 
        finally:
268
 
            wt.unlock()
269
 
 
270
 
 
271
 
class cmd_rebase_continue(Command):
272
 
    """Continue an interrupted rebase after resolving conflicts."""
273
 
    takes_options = [
274
 
        'merge-type', Option(
275
 
            'directory',
276
 
            short_name='d',
277
 
            help="Branch to replay onto, rather than the one containing the working directory.",
278
 
            type=str)]
279
 
 
280
 
    @display_command
281
 
    def run(self, merge_type=None, directory="."):
282
 
        from .rebase import (
283
 
            RebaseState1,
284
 
            WorkingTreeRevisionRewriter,
285
 
            )
286
 
        from ...workingtree import WorkingTree
287
 
        wt = WorkingTree.open_containing(directory)[0]
288
 
        wt.lock_write()
289
 
        try:
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."))
298
 
            # Read plan file
299
 
            try:
300
 
                replace_map = state.read_plan()[1]
301
 
            except NoSuchFile:
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)
308
 
        finally:
309
 
            wt.unlock()
310
 
 
311
 
 
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.
315
 
 
316
 
    """
317
 
 
318
 
    takes_options = [
319
 
        Option(
320
 
            'directory',
321
 
            short_name='d',
322
 
            help="Branch to replay onto, rather than the one containing the working directory.",
323
 
            type=str)]
324
 
 
325
 
    def run(self, directory="."):
326
 
        from .rebase import (
327
 
            RebaseState1,
328
 
            rebase_todo,
329
 
            )
330
 
        from ...workingtree import WorkingTree
331
 
        wt = WorkingTree.open_containing(directory)[0]
332
 
        with wt.lock_read():
333
 
            state = RebaseState1(wt)
334
 
            try:
335
 
                replace_map = state.read_plan()[1]
336
 
            except NoSuchFile:
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]))
343
 
 
344
 
 
345
 
class cmd_replay(Command):
346
 
    """Replay commits from another branch on top of this one.
347
 
 
348
 
    """
349
 
 
350
 
    takes_options = [
351
 
        'revision', 'merge-type',
352
 
        Option(
353
 
            'directory',
354
 
            short_name='d',
355
 
            help="Branch to replay onto, rather than the one containing the working directory.",
356
 
            type=str)]
357
 
    takes_args = ['location']
358
 
    hidden = True
359
 
 
360
 
    def run(self, location, revision=None, merge_type=None, directory="."):
361
 
        from ...branch import Branch
362
 
        from ...workingtree import WorkingTree
363
 
        from ... import ui
364
 
        from .rebase import (
365
 
            RebaseState1,
366
 
            regenerate_default_revid,
367
 
            WorkingTreeRevisionRewriter,
368
 
            )
369
 
 
370
 
        from_branch = Branch.open_containing(location)[0]
371
 
 
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)
379
 
                if to_revid is None:
380
 
                    to_revno = from_branch.revno()
381
 
                todo = []
382
 
                for revno in range(from_revno, to_revno + 1):
383
 
                    todo.append(from_branch.get_rev_id(revno))
384
 
            else:
385
 
                raise BzrCommandError(gettext(
386
 
                    "--revision takes only one or two arguments"))
387
 
        else:
388
 
            raise BzrCommandError(gettext("--revision is mandatory"))
389
 
 
390
 
        wt = WorkingTree.open(directory)
391
 
        wt.lock_write()
392
 
        try:
393
 
            state = RebaseState1(wt)
394
 
            replayer = WorkingTreeRevisionRewriter(wt, state, merge_type=merge_type)
395
 
            pb = ui.ui_factory.nested_progress_bar()
396
 
            try:
397
 
                for revid in todo:
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()])
402
 
            finally:
403
 
                pb.finished()
404
 
        finally:
405
 
            wt.unlock()
406
 
 
407
 
 
408
 
class cmd_pseudonyms(Command):
409
 
    """Show a list of 'pseudonym' revisions.
410
 
 
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.
414
 
    """
415
 
 
416
 
    takes_args = ['repository?']
417
 
    hidden = True
418
 
 
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")
426
 
 
427
 
 
428
 
class cmd_rebase_foreign(Command):
429
 
    """Rebase revisions based on a branch created with a different import tool.
430
 
 
431
 
    This will change the identity of revisions whose parents
432
 
    were mapped from revisions in the other version control system.
433
 
 
434
 
    You are recommended to run "brz check" in the local repository
435
 
    after running this command.
436
 
    """
437
 
    takes_args = ['new_base?']
438
 
    takes_options = [
439
 
        'verbose',
440
 
        Option("idmap-file", help="Write map with old and new revision ids.",
441
 
               type=str),
442
 
        Option(
443
 
            'directory',
444
 
            short_name='d',
445
 
            help="Branch to replay onto, rather than the one containing the working directory.",
446
 
            type=str)
447
 
        ]
448
 
 
449
 
    def run(self, new_base=None, verbose=False, idmap_file=None, directory="."):
450
 
        from ... import (
451
 
            urlutils,
452
 
            )
453
 
        from ...branch import Branch
454
 
        from ...workingtree import WorkingTree
455
 
        from .pseudonyms import (
456
 
            find_pseudonyms,
457
 
            generate_rebase_map_from_pseudonyms,
458
 
            pseudonyms_as_dict,
459
 
            )
460
 
        from .upgrade import (
461
 
            create_deterministic_revid,
462
 
            upgrade_branch,
463
 
            )
464
 
        from ...foreign import (
465
 
            update_workingtree_fileids,
466
 
            )
467
 
 
468
 
        try:
469
 
            wt_to = WorkingTree.open(directory)
470
 
            branch_to = wt_to.branch
471
 
        except NoWorkingTree:
472
 
            wt_to = None
473
 
            branch_to = Branch.open(directory)
474
 
 
475
 
        stored_loc = branch_to.get_parent()
476
 
        if new_base is None:
477
 
            if stored_loc is None:
478
 
                raise BzrCommandError(gettext(
479
 
                    "No pull location known or specified."))
480
 
            else:
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)
485
 
        else:
486
 
            new_base = Branch.open(new_base)
487
 
 
488
 
        branch_to.repository.fetch(
489
 
            new_base.repository, revision_id=branch_to.last_revision())
490
 
 
491
 
        pseudonyms = pseudonyms_as_dict(find_pseudonyms(
492
 
            branch_to.repository, branch_to.repository.all_revision_ids()))
493
 
 
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()
501
 
        try:
502
 
            graph = branch_to.repository.get_graph()
503
 
            renames = upgrade_branch(
504
 
                branch_to, generate_rebase_map,
505
 
                determine_new_revid, allow_changes=True,
506
 
                verbose=verbose)
507
 
            if wt_to is not None:
508
 
                basis_tree = wt_to.basis_tree()
509
 
                basis_tree.lock_read()
510
 
                try:
511
 
                    update_workingtree_fileids(wt_to, basis_tree)
512
 
                finally:
513
 
                    basis_tree.unlock()
514
 
        finally:
515
 
            branch_to.unlock()
516
 
 
517
 
        if renames == {}:
518
 
            note(gettext("Nothing to do."))
519
 
 
520
 
        if idmap_file is not None:
521
 
            f = open(idmap_file, 'w')
522
 
            try:
523
 
                for oldid, newid in renames.iteritems():
524
 
                    f.write("%s\t%s\n" % (oldid, newid))
525
 
            finally:
526
 
                f.close()
527
 
 
528
 
        if wt_to is not None:
529
 
            wt_to.set_last_revision(branch_to.last_revision())