Mercurial > hg
annotate hgext/split.py @ 42228:df7ad90edbd4
copies: delete a redundant cleanup step in _chain()
The check is redundant since d5edb5d3a337 (copies: filter out copies
when target is not in destination manifest, 2019-02-14). To test that
hypothesis, I made this change in the commit that commit, but all
tests still passed. I think the case was necessary before then, we
just didn't have tests for it.
Differential Revision: https://phab.mercurial-scm.org/D6275
author | Martin von Zweigbergk <martinvonz@google.com> |
---|---|
date | Thu, 18 Apr 2019 00:05:05 -0700 |
parents | 42e2c7c52e1b |
children | 2372284d9457 |
rev | line source |
---|---|
35455 | 1 # split.py - split a changeset into smaller ones |
2 # | |
3 # Copyright 2015 Laurent Charignon <lcharignon@fb.com> | |
4 # Copyright 2017 Facebook, Inc. | |
5 # | |
6 # This software may be used and distributed according to the terms of the | |
7 # GNU General Public License version 2 or any later version. | |
8 """command to split a changeset into smaller ones (EXPERIMENTAL)""" | |
9 | |
10 from __future__ import absolute_import | |
11 | |
12 from mercurial.i18n import _ | |
13 | |
14 from mercurial.node import ( | |
15 nullid, | |
16 short, | |
17 ) | |
18 | |
19 from mercurial import ( | |
20 bookmarks, | |
21 cmdutil, | |
22 commands, | |
23 error, | |
24 hg, | |
25 obsolete, | |
26 phases, | |
36400
7b86aa31b004
py3: fix handling of keyword arguments at more places
Pulkit Goyal <7895pulkit@gmail.com>
parents:
35455
diff
changeset
|
27 pycompat, |
35455 | 28 registrar, |
29 revsetlang, | |
30 scmutil, | |
31 ) | |
32 | |
33 # allow people to use split without explicitly enabling rebase extension | |
34 from . import ( | |
35 rebase, | |
36 ) | |
37 | |
38 cmdtable = {} | |
39 command = registrar.command(cmdtable) | |
40 | |
41 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for | |
42 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should | |
43 # be specifying the version(s) of Mercurial they are tested with, or | |
44 # leave the attribute unspecified. | |
45 testedwith = 'ships-with-hg-core' | |
46 | |
40295
fa88170c10bb
help: adding a proper declaration for shortlist/basic commands (API)
Rodrigo Damazio <rdamazio@google.com>
parents:
40293
diff
changeset
|
47 @command('split', |
35455 | 48 [('r', 'rev', '', _("revision to split"), _('REV')), |
49 ('', 'rebase', True, _('rebase descendants after split')), | |
50 ] + cmdutil.commitopts2, | |
40293
c303d65d2e34
help: assigning categories to existing commands
rdamazio@google.com
parents:
38424
diff
changeset
|
51 _('hg split [--no-rebase] [[-r] REV]'), |
40295
fa88170c10bb
help: adding a proper declaration for shortlist/basic commands (API)
Rodrigo Damazio <rdamazio@google.com>
parents:
40293
diff
changeset
|
52 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, helpbasic=True) |
35455 | 53 def split(ui, repo, *revs, **opts): |
54 """split a changeset into smaller ones | |
55 | |
56 Repeatedly prompt changes and commit message for new changesets until there | |
57 is nothing left in the original changeset. | |
58 | |
59 If --rev was not given, split the working directory parent. | |
60 | |
61 By default, rebase connected non-obsoleted descendants onto the new | |
62 changeset. Use --no-rebase to avoid the rebase. | |
63 """ | |
38069
5ba0cf22e4d0
py3: fix kwargs handling in hgext/split.py
Pulkit Goyal <7895pulkit@gmail.com>
parents:
36408
diff
changeset
|
64 opts = pycompat.byteskwargs(opts) |
35455 | 65 revlist = [] |
66 if opts.get('rev'): | |
67 revlist.append(opts.get('rev')) | |
68 revlist.extend(revs) | |
69 with repo.wlock(), repo.lock(), repo.transaction('split') as tr: | |
70 revs = scmutil.revrange(repo, revlist or ['.']) | |
71 if len(revs) > 1: | |
72 raise error.Abort(_('cannot split multiple revisions')) | |
73 | |
74 rev = revs.first() | |
75 ctx = repo[rev] | |
76 if rev is None or ctx.node() == nullid: | |
77 ui.status(_('nothing to split\n')) | |
78 return 1 | |
79 if ctx.node() is None: | |
80 raise error.Abort(_('cannot split working directory')) | |
81 | |
82 # rewriteutil.precheck is not very useful here because: | |
83 # 1. null check is done above and it's more friendly to return 1 | |
84 # instead of abort | |
85 # 2. mergestate check is done below by cmdutil.bailifchanged | |
86 # 3. unstable check is more complex here because of --rebase | |
87 # | |
88 # So only "public" check is useful and it's checked directly here. | |
89 if ctx.phase() == phases.public: | |
90 raise error.Abort(_('cannot split public changeset'), | |
91 hint=_("see 'hg help phases' for details")) | |
92 | |
93 descendants = list(repo.revs('(%d::) - (%d)', rev, rev)) | |
94 alloworphaned = obsolete.isenabled(repo, obsolete.allowunstableopt) | |
95 if opts.get('rebase'): | |
96 # Skip obsoleted descendants and their descendants so the rebase | |
97 # won't cause conflicts for sure. | |
98 torebase = list(repo.revs('%ld - (%ld & obsolete())::', | |
99 descendants, descendants)) | |
100 if not alloworphaned and len(torebase) != len(descendants): | |
101 raise error.Abort(_('split would leave orphaned changesets ' | |
102 'behind')) | |
103 else: | |
104 if not alloworphaned and descendants: | |
105 raise error.Abort( | |
106 _('cannot split changeset with children without rebase')) | |
107 torebase = () | |
108 | |
109 if len(ctx.parents()) > 1: | |
110 raise error.Abort(_('cannot split a merge changeset')) | |
111 | |
112 cmdutil.bailifchanged(repo) | |
113 | |
114 # Deactivate bookmark temporarily so it won't get moved unintentionally | |
115 bname = repo._activebookmark | |
116 if bname and repo._bookmarks[bname] != ctx.node(): | |
117 bookmarks.deactivate(repo) | |
118 | |
119 wnode = repo['.'].node() | |
120 top = None | |
121 try: | |
122 top = dosplit(ui, repo, tr, ctx, opts) | |
123 finally: | |
124 # top is None: split failed, need update --clean recovery. | |
125 # wnode == ctx.node(): wnode split, no need to update. | |
126 if top is None or wnode != ctx.node(): | |
127 hg.clean(repo, wnode, show_stats=False) | |
128 if bname: | |
129 bookmarks.activate(repo, bname) | |
130 if torebase and top: | |
131 dorebase(ui, repo, torebase, top) | |
132 | |
133 def dosplit(ui, repo, tr, ctx, opts): | |
134 committed = [] # [ctx] | |
135 | |
136 # Set working parent to ctx.p1(), and keep working copy as ctx's content | |
41966
42e2c7c52e1b
split: use the new movedirstate() we now have in scmutil
Martin von Zweigbergk <martinvonz@google.com>
parents:
40295
diff
changeset
|
137 if ctx.node() != repo.dirstate.p1(): |
42e2c7c52e1b
split: use the new movedirstate() we now have in scmutil
Martin von Zweigbergk <martinvonz@google.com>
parents:
40295
diff
changeset
|
138 hg.clean(repo, ctx.node(), show_stats=False) |
42e2c7c52e1b
split: use the new movedirstate() we now have in scmutil
Martin von Zweigbergk <martinvonz@google.com>
parents:
40295
diff
changeset
|
139 with repo.dirstate.parentchange(): |
42e2c7c52e1b
split: use the new movedirstate() we now have in scmutil
Martin von Zweigbergk <martinvonz@google.com>
parents:
40295
diff
changeset
|
140 scmutil.movedirstate(repo, ctx.p1()) |
35455 | 141 |
142 # Any modified, added, removed, deleted result means split is incomplete | |
143 incomplete = lambda repo: any(repo.status()[:4]) | |
144 | |
145 # Main split loop | |
146 while incomplete(repo): | |
147 if committed: | |
148 header = (_('HG: Splitting %s. So far it has been split into:\n') | |
149 % short(ctx.node())) | |
150 for c in committed: | |
151 firstline = c.description().split('\n', 1)[0] | |
152 header += _('HG: - %s: %s\n') % (short(c.node()), firstline) | |
153 header += _('HG: Write commit message for the next split ' | |
154 'changeset.\n') | |
155 else: | |
156 header = _('HG: Splitting %s. Write commit message for the ' | |
157 'first split changeset.\n') % short(ctx.node()) | |
158 opts.update({ | |
159 'edit': True, | |
160 'interactive': True, | |
161 'message': header + ctx.description(), | |
162 }) | |
36400
7b86aa31b004
py3: fix handling of keyword arguments at more places
Pulkit Goyal <7895pulkit@gmail.com>
parents:
35455
diff
changeset
|
163 commands.commit(ui, repo, **pycompat.strkwargs(opts)) |
35455 | 164 newctx = repo['.'] |
165 committed.append(newctx) | |
166 | |
167 if not committed: | |
168 raise error.Abort(_('cannot split an empty revision')) | |
169 | |
170 scmutil.cleanupnodes(repo, {ctx.node(): [c.node() for c in committed]}, | |
38424
4f885770c4a2
split: preserve phase of commit that is being split
Martin von Zweigbergk <martinvonz@google.com>
parents:
38069
diff
changeset
|
171 operation='split', fixphase=True) |
35455 | 172 |
173 return committed[-1] | |
174 | |
36408
83bade6206d4
split: use ctx.rev() instead of %d % ctx
Gregory Szorc <gregory.szorc@gmail.com>
parents:
36400
diff
changeset
|
175 def dorebase(ui, repo, src, destctx): |
35455 | 176 rebase.rebase(ui, repo, rev=[revsetlang.formatspec('%ld', src)], |
36408
83bade6206d4
split: use ctx.rev() instead of %d % ctx
Gregory Szorc <gregory.szorc@gmail.com>
parents:
36400
diff
changeset
|
177 dest=revsetlang.formatspec('%d', destctx.rev())) |