/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

  • Committer: Jelmer Vernooij
  • Date: 2020-05-06 02:13:25 UTC
  • mfrom: (7490.7.21 work)
  • mto: This revision was merged to the branch mainline in revision 7501.
  • Revision ID: jelmer@jelmer.uk-20200506021325-awbmmqu1zyorz7sj
Merge 3.1 branch.

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 ...commands import (
 
20
    Command,
 
21
    display_command,
 
22
    )
 
23
from ...errors import (
 
24
    BzrCommandError,
 
25
    ConflictsInTree,
 
26
    NoSuchFile,
 
27
    NoWorkingTree,
 
28
    UncommittedChanges,
 
29
    )
 
30
from ...option import (
 
31
    Option,
 
32
    )
 
33
from ...trace import (
 
34
    note,
 
35
    )
 
36
 
 
37
from ...i18n import gettext
 
38
 
 
39
 
 
40
def finish_rebase(state, wt, replace_map, replayer):
 
41
    from .rebase import (
 
42
        rebase,
 
43
        )
 
44
    try:
 
45
        # Start executing plan from current Branch.last_revision()
 
46
        rebase(wt.branch.repository, replace_map, replayer)
 
47
    except ConflictsInTree:
 
48
        raise BzrCommandError(gettext(
 
49
            "A conflict occurred replaying a commit."
 
50
            " Resolve the conflict and run 'brz rebase-continue' or "
 
51
            "run 'brz rebase-abort'."))
 
52
    # Remove plan file
 
53
    state.remove_plan()
 
54
 
 
55
 
 
56
class cmd_rebase(Command):
 
57
    """Re-base a branch.
 
58
 
 
59
    Rebasing is the process of taking a branch and modifying the history so
 
60
    that it appears to start from a different point. This can be useful
 
61
    to clean up the history before submitting your changes. The tree at the
 
62
    end of the process will be the same as if you had merged the other branch,
 
63
    but the history will be different.
 
64
 
 
65
    The command takes the location of another branch on to which the branch in
 
66
    the specified directory (by default, the current working directory)
 
67
    will be rebased. If a branch is not specified then the parent branch
 
68
    is used, and this is usually the desired result.
 
69
 
 
70
    The first step identifies the revisions that are in the current branch that
 
71
    are not in the parent branch. The current branch is then set to be at the
 
72
    same revision as the target branch, and each revision is replayed on top
 
73
    of the branch. At the end of the process it will appear as though your
 
74
    current branch was branched off the current last revision of the target.
 
75
 
 
76
    Each revision that is replayed may cause conflicts in the tree. If this
 
77
    happens the command will stop and allow you to fix them up. Resolve the
 
78
    commits as you would for a merge, and then run 'brz resolve' to marked
 
79
    them as resolved. Once you have resolved all the conflicts you should
 
80
    run 'brz rebase-continue' to continue the rebase operation.
 
81
 
 
82
    If conflicts are encountered and you decide that you do not wish to continue
 
83
    you can run 'brz rebase-abort'.
 
84
 
 
85
    The '--onto' option allows you to specify a different revision in the
 
86
    target branch to start at when replaying the revisions. This means that
 
87
    you can change the point at which the current branch will appear to be
 
88
    branched from when the operation completes.
 
89
    """
 
90
    takes_args = ['upstream_location?']
 
91
    takes_options = [
 
92
        'revision', 'merge-type', 'verbose',
 
93
        Option(
 
94
            'dry-run',
 
95
            help="Show what would be done, but don't actually do anything."),
 
96
        Option(
 
97
            'always-rebase-merges',
 
98
            help="Don't skip revisions that merge already present revisions."),
 
99
        Option(
 
100
            'pending-merges',
 
101
            help="Rebase pending merges onto local branch."),
 
102
        Option(
 
103
            'onto', help='Different revision to replay onto.',
 
104
            type=str),
 
105
        Option(
 
106
            'directory',
 
107
            short_name='d',
 
108
            help="Branch to replay onto, rather than the one containing the working directory.",
 
109
            type=str)
 
110
        ]
 
111
 
 
112
    @display_command
 
113
    def run(self, upstream_location=None, onto=None, revision=None,
 
114
            merge_type=None, verbose=False, dry_run=False,
 
115
            always_rebase_merges=False, pending_merges=False,
 
116
            directory="."):
 
117
        from ...branch import Branch
 
118
        from ...revisionspec import RevisionSpec
 
119
        from ...workingtree import WorkingTree
 
120
        from .rebase import (
 
121
            generate_simple_plan,
 
122
            rebase,
 
123
            RebaseState1,
 
124
            WorkingTreeRevisionRewriter,
 
125
            regenerate_default_revid,
 
126
            rebase_todo,
 
127
            )
 
128
        if revision is not None and pending_merges:
 
129
            raise BzrCommandError(gettext(
 
130
                "--revision and --pending-merges are mutually exclusive"))
 
131
 
 
132
        wt = WorkingTree.open_containing(directory)[0]
 
133
        wt.lock_write()
 
134
        try:
 
135
            state = RebaseState1(wt)
 
136
            if upstream_location is None:
 
137
                if pending_merges:
 
138
                    upstream_location = directory
 
139
                else:
 
140
                    upstream_location = wt.branch.get_parent()
 
141
                    if upstream_location is None:
 
142
                        raise BzrCommandError(gettext("No upstream branch specified."))
 
143
                    note(gettext("Rebasing on %s"), upstream_location)
 
144
            upstream = Branch.open_containing(upstream_location)[0]
 
145
            upstream_repository = upstream.repository
 
146
            upstream_revision = upstream.last_revision()
 
147
            # Abort if there already is a plan file
 
148
            if state.has_plan():
 
149
                raise BzrCommandError(gettext(
 
150
                    "A rebase operation was interrupted. "
 
151
                    "Continue using 'brz rebase-continue' or abort using 'brz "
 
152
                    "rebase-abort'"))
 
153
 
 
154
            start_revid = None
 
155
            stop_revid = None
 
156
            if revision is not None:
 
157
                if len(revision) == 1:
 
158
                    if revision[0] is not None:
 
159
                        stop_revid = revision[0].as_revision_id(wt.branch)
 
160
                elif len(revision) == 2:
 
161
                    if revision[0] is not None:
 
162
                        start_revid = revision[0].as_revision_id(wt.branch)
 
163
                    if revision[1] is not None:
 
164
                        stop_revid = revision[1].as_revision_id(wt.branch)
 
165
                else:
 
166
                    raise BzrCommandError(gettext(
 
167
                        "--revision takes only one or two arguments"))
 
168
 
 
169
            if pending_merges:
 
170
                wt_parents = wt.get_parent_ids()
 
171
                if len(wt_parents) in (0, 1):
 
172
                    raise BzrCommandError(gettext("No pending merges present."))
 
173
                elif len(wt_parents) > 2:
 
174
                    raise BzrCommandError(gettext(
 
175
                        "Rebasing more than one pending merge not supported"))
 
176
                stop_revid = wt_parents[1]
 
177
                assert stop_revid is not None, "stop revid invalid"
 
178
 
 
179
            # Check for changes in the working tree.
 
180
            if (not pending_merges and
 
181
                    wt.basis_tree().changes_from(wt).has_changed()):
 
182
                raise UncommittedChanges(wt)
 
183
 
 
184
            # Pull required revisions
 
185
            wt.branch.repository.fetch(upstream_repository, upstream_revision)
 
186
            if onto is None:
 
187
                onto = upstream.last_revision()
 
188
            else:
 
189
                rev_spec = RevisionSpec.from_string(onto)
 
190
                onto = rev_spec.as_revision_id(upstream)
 
191
 
 
192
            wt.branch.repository.fetch(upstream_repository, revision_id=onto)
 
193
 
 
194
            if stop_revid is None:
 
195
                stop_revid = wt.branch.last_revision()
 
196
            repo_graph = wt.branch.repository.get_graph()
 
197
            our_new, onto_unique = repo_graph.find_difference(stop_revid, onto)
 
198
 
 
199
            if start_revid is None:
 
200
                if not onto_unique:
 
201
                    self.outf.write(gettext("No revisions to rebase.\n"))
 
202
                    return
 
203
                if not our_new:
 
204
                    self.outf.write(gettext(
 
205
                        "Base branch is descendant of current "
 
206
                        "branch. Pulling instead.\n"))
 
207
                    if not dry_run:
 
208
                        wt.pull(upstream, stop_revision=onto)
 
209
                    return
 
210
            # else: include extra revisions needed to make start_revid mean
 
211
            # something.
 
212
 
 
213
            # Create plan
 
214
            replace_map = generate_simple_plan(
 
215
                our_new, start_revid, stop_revid, onto, repo_graph,
 
216
                lambda revid, ps: regenerate_default_revid(
 
217
                    wt.branch.repository, revid),
 
218
                not always_rebase_merges)
 
219
 
 
220
            if verbose or dry_run:
 
221
                todo = list(rebase_todo(wt.branch.repository, replace_map))
 
222
                note(gettext('%d revisions will be rebased:') % len(todo))
 
223
                for revid in todo:
 
224
                    note("%s" % revid)
 
225
 
 
226
            if not dry_run:
 
227
                # Write plan file
 
228
                state.write_plan(replace_map)
 
229
 
 
230
                replayer = WorkingTreeRevisionRewriter(wt, state, merge_type=merge_type)
 
231
 
 
232
                finish_rebase(state, wt, replace_map, replayer)
 
233
        finally:
 
234
            wt.unlock()
 
235
 
 
236
 
 
237
class cmd_rebase_abort(Command):
 
238
    """Abort an interrupted rebase."""
 
239
 
 
240
    takes_options = [
 
241
        Option(
 
242
            'directory',
 
243
            short_name='d',
 
244
            help="Branch to replay onto, rather than the one containing the working directory.",
 
245
            type=str)]
 
246
 
 
247
    @display_command
 
248
    def run(self, directory="."):
 
249
        from .rebase import (
 
250
            RebaseState1,
 
251
            complete_revert,
 
252
            )
 
253
        from ...workingtree import WorkingTree
 
254
        wt = WorkingTree.open_containing(directory)[0]
 
255
        wt.lock_write()
 
256
        try:
 
257
            state = RebaseState1(wt)
 
258
            # Read plan file and set last revision
 
259
            try:
 
260
                last_rev_info = state.read_plan()[0]
 
261
            except NoSuchFile:
 
262
                raise BzrCommandError("No rebase to abort")
 
263
            complete_revert(wt, [last_rev_info[1]])
 
264
            state.remove_plan()
 
265
        finally:
 
266
            wt.unlock()
 
267
 
 
268
 
 
269
class cmd_rebase_continue(Command):
 
270
    """Continue an interrupted rebase after resolving conflicts."""
 
271
    takes_options = [
 
272
        'merge-type', Option(
 
273
            'directory',
 
274
            short_name='d',
 
275
            help="Branch to replay onto, rather than the one containing the working directory.",
 
276
            type=str)]
 
277
 
 
278
    @display_command
 
279
    def run(self, merge_type=None, directory="."):
 
280
        from .rebase import (
 
281
            RebaseState1,
 
282
            WorkingTreeRevisionRewriter,
 
283
            )
 
284
        from ...workingtree import WorkingTree
 
285
        wt = WorkingTree.open_containing(directory)[0]
 
286
        wt.lock_write()
 
287
        try:
 
288
            state = RebaseState1(wt)
 
289
            replayer = WorkingTreeRevisionRewriter(wt, state, merge_type=merge_type)
 
290
            # Abort if there are any conflicts
 
291
            if len(wt.conflicts()) != 0:
 
292
                raise BzrCommandError(gettext(
 
293
                    "There are still conflicts present. "
 
294
                    "Resolve the conflicts and then run "
 
295
                    "'brz resolve' and try again."))
 
296
            # Read plan file
 
297
            try:
 
298
                replace_map = state.read_plan()[1]
 
299
            except NoSuchFile:
 
300
                raise BzrCommandError(gettext("No rebase to continue"))
 
301
            oldrevid = state.read_active_revid()
 
302
            if oldrevid is not None:
 
303
                oldrev = wt.branch.repository.get_revision(oldrevid)
 
304
                replayer.commit_rebase(oldrev, replace_map[oldrevid][0])
 
305
            finish_rebase(state, wt, replace_map, replayer)
 
306
        finally:
 
307
            wt.unlock()
 
308
 
 
309
 
 
310
class cmd_rebase_todo(Command):
 
311
    """Print list of revisions that still need to be replayed as part of the
 
312
    current rebase operation.
 
313
 
 
314
    """
 
315
 
 
316
    takes_options = [
 
317
        Option(
 
318
            'directory',
 
319
            short_name='d',
 
320
            help="Branch to replay onto, rather than the one containing the working directory.",
 
321
            type=str)]
 
322
 
 
323
    def run(self, directory="."):
 
324
        from .rebase import (
 
325
            RebaseState1,
 
326
            rebase_todo,
 
327
            )
 
328
        from ...workingtree import WorkingTree
 
329
        wt = WorkingTree.open_containing(directory)[0]
 
330
        with wt.lock_read():
 
331
            state = RebaseState1(wt)
 
332
            try:
 
333
                replace_map = state.read_plan()[1]
 
334
            except NoSuchFile:
 
335
                raise BzrCommandError(gettext("No rebase in progress"))
 
336
            currentrevid = state.read_active_revid()
 
337
            if currentrevid is not None:
 
338
                note(gettext("Currently replaying: %s") % currentrevid)
 
339
            for revid in rebase_todo(wt.branch.repository, replace_map):
 
340
                note(gettext("{0} -> {1}").format(revid, replace_map[revid][0]))
 
341
 
 
342
 
 
343
class cmd_replay(Command):
 
344
    """Replay commits from another branch on top of this one.
 
345
 
 
346
    """
 
347
 
 
348
    takes_options = [
 
349
        'revision', 'merge-type',
 
350
        Option(
 
351
            'directory',
 
352
            short_name='d',
 
353
            help="Branch to replay onto, rather than the one containing the working directory.",
 
354
            type=str)]
 
355
    takes_args = ['location']
 
356
    hidden = True
 
357
 
 
358
    def run(self, location, revision=None, merge_type=None, directory="."):
 
359
        from ...branch import Branch
 
360
        from ...workingtree import WorkingTree
 
361
        from ... import ui
 
362
        from .rebase import (
 
363
            RebaseState1,
 
364
            regenerate_default_revid,
 
365
            WorkingTreeRevisionRewriter,
 
366
            )
 
367
 
 
368
        from_branch = Branch.open_containing(location)[0]
 
369
 
 
370
        if revision is not None:
 
371
            if len(revision) == 1:
 
372
                if revision[0] is not None:
 
373
                    todo = [revision[0].as_revision_id(from_branch)]
 
374
            elif len(revision) == 2:
 
375
                from_revno, from_revid = revision[0].in_history(from_branch)
 
376
                to_revno, to_revid = revision[1].in_history(from_branch)
 
377
                if to_revid is None:
 
378
                    to_revno = from_branch.revno()
 
379
                todo = []
 
380
                for revno in range(from_revno, to_revno + 1):
 
381
                    todo.append(from_branch.get_rev_id(revno))
 
382
            else:
 
383
                raise BzrCommandError(gettext(
 
384
                    "--revision takes only one or two arguments"))
 
385
        else:
 
386
            raise BzrCommandError(gettext("--revision is mandatory"))
 
387
 
 
388
        wt = WorkingTree.open(directory)
 
389
        wt.lock_write()
 
390
        try:
 
391
            state = RebaseState1(wt)
 
392
            replayer = WorkingTreeRevisionRewriter(wt, state, merge_type=merge_type)
 
393
            pb = ui.ui_factory.nested_progress_bar()
 
394
            try:
 
395
                for revid in todo:
 
396
                    pb.update(gettext("replaying commits"), todo.index(revid), len(todo))
 
397
                    wt.branch.repository.fetch(from_branch.repository, revid)
 
398
                    newrevid = regenerate_default_revid(wt.branch.repository, revid)
 
399
                    replayer(revid, newrevid, [wt.last_revision()])
 
400
            finally:
 
401
                pb.finished()
 
402
        finally:
 
403
            wt.unlock()
 
404
 
 
405
 
 
406
class cmd_pseudonyms(Command):
 
407
    """Show a list of 'pseudonym' revisions.
 
408
 
 
409
    Pseudonym revisions are revisions that are roughly the same revision,
 
410
    usually because they were converted from the same revision in a
 
411
    foreign version control system.
 
412
    """
 
413
 
 
414
    takes_args = ['repository?']
 
415
    hidden = True
 
416
 
 
417
    def run(self, repository=None):
 
418
        from ...controldir import ControlDir
 
419
        dir, _ = ControlDir.open_containing(repository)
 
420
        r = dir.find_repository()
 
421
        from .pseudonyms import find_pseudonyms
 
422
        for pseudonyms in find_pseudonyms(r, r.all_revision_ids()):
 
423
            self.outf.write(", ".join(pseudonyms) + "\n")
 
424
 
 
425
 
 
426
class cmd_rebase_foreign(Command):
 
427
    """Rebase revisions based on a branch created with a different import tool.
 
428
 
 
429
    This will change the identity of revisions whose parents
 
430
    were mapped from revisions in the other version control system.
 
431
 
 
432
    You are recommended to run "brz check" in the local repository
 
433
    after running this command.
 
434
    """
 
435
    takes_args = ['new_base?']
 
436
    takes_options = [
 
437
        'verbose',
 
438
        Option("idmap-file", help="Write map with old and new revision ids.",
 
439
               type=str),
 
440
        Option(
 
441
            'directory',
 
442
            short_name='d',
 
443
            help="Branch to replay onto, rather than the one containing the working directory.",
 
444
            type=str)
 
445
        ]
 
446
 
 
447
    def run(self, new_base=None, verbose=False, idmap_file=None, directory="."):
 
448
        from ... import (
 
449
            urlutils,
 
450
            )
 
451
        from ...branch import Branch
 
452
        from ...workingtree import WorkingTree
 
453
        from .pseudonyms import (
 
454
            find_pseudonyms,
 
455
            generate_rebase_map_from_pseudonyms,
 
456
            pseudonyms_as_dict,
 
457
            )
 
458
        from .upgrade import (
 
459
            create_deterministic_revid,
 
460
            upgrade_branch,
 
461
            )
 
462
        from ...foreign import (
 
463
            update_workingtree_fileids,
 
464
            )
 
465
 
 
466
        try:
 
467
            wt_to = WorkingTree.open(directory)
 
468
            branch_to = wt_to.branch
 
469
        except NoWorkingTree:
 
470
            wt_to = None
 
471
            branch_to = Branch.open(directory)
 
472
 
 
473
        stored_loc = branch_to.get_parent()
 
474
        if new_base is None:
 
475
            if stored_loc is None:
 
476
                raise BzrCommandError(gettext(
 
477
                    "No pull location known or specified."))
 
478
            else:
 
479
                display_url = urlutils.unescape_for_display(
 
480
                    stored_loc, self.outf.encoding)
 
481
                self.outf.write(gettext("Using saved location: %s\n") % display_url)
 
482
                new_base = Branch.open(stored_loc)
 
483
        else:
 
484
            new_base = Branch.open(new_base)
 
485
 
 
486
        branch_to.repository.fetch(
 
487
            new_base.repository, revision_id=branch_to.last_revision())
 
488
 
 
489
        pseudonyms = pseudonyms_as_dict(find_pseudonyms(
 
490
            branch_to.repository, branch_to.repository.all_revision_ids()))
 
491
 
 
492
        def generate_rebase_map(revision_id):
 
493
            return generate_rebase_map_from_pseudonyms(
 
494
                pseudonyms, branch_to.repository.get_ancestry(revision_id),
 
495
                branch_to.repository.get_ancestry(new_base.last_revision()))
 
496
        def determine_new_revid(old_revid, new_parents):
 
497
            return create_deterministic_revid(old_revid, new_parents)
 
498
        branch_to.lock_write()
 
499
        try:
 
500
            graph = branch_to.repository.get_graph()
 
501
            renames = upgrade_branch(
 
502
                branch_to, generate_rebase_map,
 
503
                determine_new_revid, allow_changes=True,
 
504
                verbose=verbose)
 
505
            if wt_to is not None:
 
506
                basis_tree = wt_to.basis_tree()
 
507
                basis_tree.lock_read()
 
508
                try:
 
509
                    update_workingtree_fileids(wt_to, basis_tree)
 
510
                finally:
 
511
                    basis_tree.unlock()
 
512
        finally:
 
513
            branch_to.unlock()
 
514
 
 
515
        if renames == {}:
 
516
            note(gettext("Nothing to do."))
 
517
 
 
518
        if idmap_file is not None:
 
519
            f = open(idmap_file, 'w')
 
520
            try:
 
521
                for oldid, newid in renames.iteritems():
 
522
                    f.write("%s\t%s\n" % (oldid, newid))
 
523
            finally:
 
524
                f.close()
 
525
 
 
526
        if wt_to is not None:
 
527
            wt_to.set_last_revision(branch_to.last_revision())