/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: 2018-02-18 21:42:57 UTC
  • mto: This revision was merged to the branch mainline in revision 6859.
  • Revision ID: jelmer@jelmer.uk-20180218214257-jpevutp1wa30tz3v
Update TODO to reference Breezy, not Bazaar.

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
 
    CommandError,
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 CommandError(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 CommandError(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 CommandError(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 CommandError(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 CommandError(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 CommandError(gettext("No pending merges present."))
173
 
                elif len(wt_parents) > 2:
174
 
                    raise CommandError(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 CommandError("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 CommandError(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 CommandError(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 CommandError(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 CommandError(gettext(
384
 
                    "--revision takes only one or two arguments"))
385
 
        else:
386
 
            raise CommandError(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 CommandError(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())