/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-04-05 19:11:34 UTC
  • mto: (7490.7.16 work)
  • mto: This revision was merged to the branch mainline in revision 7501.
  • Revision ID: jelmer@jelmer.uk-20200405191134-0aebh8ikiwygxma5
Populate the .gitignore file.

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())