##// END OF EJS Templates
split: use field names instead of field numbers on scmutil.status...
Augie Fackler -
r44040:705738de default
parent child Browse files
Show More
@@ -1,204 +1,206 b''
1 # split.py - split a changeset into smaller ones
1 # split.py - split a changeset into smaller ones
2 #
2 #
3 # Copyright 2015 Laurent Charignon <lcharignon@fb.com>
3 # Copyright 2015 Laurent Charignon <lcharignon@fb.com>
4 # Copyright 2017 Facebook, Inc.
4 # Copyright 2017 Facebook, Inc.
5 #
5 #
6 # This software may be used and distributed according to the terms of the
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.
7 # GNU General Public License version 2 or any later version.
8 """command to split a changeset into smaller ones (EXPERIMENTAL)"""
8 """command to split a changeset into smaller ones (EXPERIMENTAL)"""
9
9
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13
13
14 from mercurial.node import (
14 from mercurial.node import (
15 nullid,
15 nullid,
16 short,
16 short,
17 )
17 )
18
18
19 from mercurial import (
19 from mercurial import (
20 bookmarks,
20 bookmarks,
21 cmdutil,
21 cmdutil,
22 commands,
22 commands,
23 error,
23 error,
24 hg,
24 hg,
25 obsolete,
25 obsolete,
26 phases,
26 phases,
27 pycompat,
27 pycompat,
28 registrar,
28 registrar,
29 revsetlang,
29 revsetlang,
30 scmutil,
30 scmutil,
31 )
31 )
32
32
33 # allow people to use split without explicitly enabling rebase extension
33 # allow people to use split without explicitly enabling rebase extension
34 from . import rebase
34 from . import rebase
35
35
36 cmdtable = {}
36 cmdtable = {}
37 command = registrar.command(cmdtable)
37 command = registrar.command(cmdtable)
38
38
39 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
39 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
40 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
40 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
41 # be specifying the version(s) of Mercurial they are tested with, or
41 # be specifying the version(s) of Mercurial they are tested with, or
42 # leave the attribute unspecified.
42 # leave the attribute unspecified.
43 testedwith = b'ships-with-hg-core'
43 testedwith = b'ships-with-hg-core'
44
44
45
45
46 @command(
46 @command(
47 b'split',
47 b'split',
48 [
48 [
49 (b'r', b'rev', b'', _(b"revision to split"), _(b'REV')),
49 (b'r', b'rev', b'', _(b"revision to split"), _(b'REV')),
50 (b'', b'rebase', True, _(b'rebase descendants after split')),
50 (b'', b'rebase', True, _(b'rebase descendants after split')),
51 ]
51 ]
52 + cmdutil.commitopts2,
52 + cmdutil.commitopts2,
53 _(b'hg split [--no-rebase] [[-r] REV]'),
53 _(b'hg split [--no-rebase] [[-r] REV]'),
54 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
54 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
55 helpbasic=True,
55 helpbasic=True,
56 )
56 )
57 def split(ui, repo, *revs, **opts):
57 def split(ui, repo, *revs, **opts):
58 """split a changeset into smaller ones
58 """split a changeset into smaller ones
59
59
60 Repeatedly prompt changes and commit message for new changesets until there
60 Repeatedly prompt changes and commit message for new changesets until there
61 is nothing left in the original changeset.
61 is nothing left in the original changeset.
62
62
63 If --rev was not given, split the working directory parent.
63 If --rev was not given, split the working directory parent.
64
64
65 By default, rebase connected non-obsoleted descendants onto the new
65 By default, rebase connected non-obsoleted descendants onto the new
66 changeset. Use --no-rebase to avoid the rebase.
66 changeset. Use --no-rebase to avoid the rebase.
67 """
67 """
68 opts = pycompat.byteskwargs(opts)
68 opts = pycompat.byteskwargs(opts)
69 revlist = []
69 revlist = []
70 if opts.get(b'rev'):
70 if opts.get(b'rev'):
71 revlist.append(opts.get(b'rev'))
71 revlist.append(opts.get(b'rev'))
72 revlist.extend(revs)
72 revlist.extend(revs)
73 with repo.wlock(), repo.lock(), repo.transaction(b'split') as tr:
73 with repo.wlock(), repo.lock(), repo.transaction(b'split') as tr:
74 revs = scmutil.revrange(repo, revlist or [b'.'])
74 revs = scmutil.revrange(repo, revlist or [b'.'])
75 if len(revs) > 1:
75 if len(revs) > 1:
76 raise error.Abort(_(b'cannot split multiple revisions'))
76 raise error.Abort(_(b'cannot split multiple revisions'))
77
77
78 rev = revs.first()
78 rev = revs.first()
79 ctx = repo[rev]
79 ctx = repo[rev]
80 if rev is None or ctx.node() == nullid:
80 if rev is None or ctx.node() == nullid:
81 ui.status(_(b'nothing to split\n'))
81 ui.status(_(b'nothing to split\n'))
82 return 1
82 return 1
83 if ctx.node() is None:
83 if ctx.node() is None:
84 raise error.Abort(_(b'cannot split working directory'))
84 raise error.Abort(_(b'cannot split working directory'))
85
85
86 # rewriteutil.precheck is not very useful here because:
86 # rewriteutil.precheck is not very useful here because:
87 # 1. null check is done above and it's more friendly to return 1
87 # 1. null check is done above and it's more friendly to return 1
88 # instead of abort
88 # instead of abort
89 # 2. mergestate check is done below by cmdutil.bailifchanged
89 # 2. mergestate check is done below by cmdutil.bailifchanged
90 # 3. unstable check is more complex here because of --rebase
90 # 3. unstable check is more complex here because of --rebase
91 #
91 #
92 # So only "public" check is useful and it's checked directly here.
92 # So only "public" check is useful and it's checked directly here.
93 if ctx.phase() == phases.public:
93 if ctx.phase() == phases.public:
94 raise error.Abort(
94 raise error.Abort(
95 _(b'cannot split public changeset'),
95 _(b'cannot split public changeset'),
96 hint=_(b"see 'hg help phases' for details"),
96 hint=_(b"see 'hg help phases' for details"),
97 )
97 )
98
98
99 descendants = list(repo.revs(b'(%d::) - (%d)', rev, rev))
99 descendants = list(repo.revs(b'(%d::) - (%d)', rev, rev))
100 alloworphaned = obsolete.isenabled(repo, obsolete.allowunstableopt)
100 alloworphaned = obsolete.isenabled(repo, obsolete.allowunstableopt)
101 if opts.get(b'rebase'):
101 if opts.get(b'rebase'):
102 # Skip obsoleted descendants and their descendants so the rebase
102 # Skip obsoleted descendants and their descendants so the rebase
103 # won't cause conflicts for sure.
103 # won't cause conflicts for sure.
104 torebase = list(
104 torebase = list(
105 repo.revs(
105 repo.revs(
106 b'%ld - (%ld & obsolete())::', descendants, descendants
106 b'%ld - (%ld & obsolete())::', descendants, descendants
107 )
107 )
108 )
108 )
109 if not alloworphaned and len(torebase) != len(descendants):
109 if not alloworphaned and len(torebase) != len(descendants):
110 raise error.Abort(
110 raise error.Abort(
111 _(b'split would leave orphaned changesets behind')
111 _(b'split would leave orphaned changesets behind')
112 )
112 )
113 else:
113 else:
114 if not alloworphaned and descendants:
114 if not alloworphaned and descendants:
115 raise error.Abort(
115 raise error.Abort(
116 _(b'cannot split changeset with children without rebase')
116 _(b'cannot split changeset with children without rebase')
117 )
117 )
118 torebase = ()
118 torebase = ()
119
119
120 if len(ctx.parents()) > 1:
120 if len(ctx.parents()) > 1:
121 raise error.Abort(_(b'cannot split a merge changeset'))
121 raise error.Abort(_(b'cannot split a merge changeset'))
122
122
123 cmdutil.bailifchanged(repo)
123 cmdutil.bailifchanged(repo)
124
124
125 # Deactivate bookmark temporarily so it won't get moved unintentionally
125 # Deactivate bookmark temporarily so it won't get moved unintentionally
126 bname = repo._activebookmark
126 bname = repo._activebookmark
127 if bname and repo._bookmarks[bname] != ctx.node():
127 if bname and repo._bookmarks[bname] != ctx.node():
128 bookmarks.deactivate(repo)
128 bookmarks.deactivate(repo)
129
129
130 wnode = repo[b'.'].node()
130 wnode = repo[b'.'].node()
131 top = None
131 top = None
132 try:
132 try:
133 top = dosplit(ui, repo, tr, ctx, opts)
133 top = dosplit(ui, repo, tr, ctx, opts)
134 finally:
134 finally:
135 # top is None: split failed, need update --clean recovery.
135 # top is None: split failed, need update --clean recovery.
136 # wnode == ctx.node(): wnode split, no need to update.
136 # wnode == ctx.node(): wnode split, no need to update.
137 if top is None or wnode != ctx.node():
137 if top is None or wnode != ctx.node():
138 hg.clean(repo, wnode, show_stats=False)
138 hg.clean(repo, wnode, show_stats=False)
139 if bname:
139 if bname:
140 bookmarks.activate(repo, bname)
140 bookmarks.activate(repo, bname)
141 if torebase and top:
141 if torebase and top:
142 dorebase(ui, repo, torebase, top)
142 dorebase(ui, repo, torebase, top)
143
143
144
144
145 def dosplit(ui, repo, tr, ctx, opts):
145 def dosplit(ui, repo, tr, ctx, opts):
146 committed = [] # [ctx]
146 committed = [] # [ctx]
147
147
148 # Set working parent to ctx.p1(), and keep working copy as ctx's content
148 # Set working parent to ctx.p1(), and keep working copy as ctx's content
149 if ctx.node() != repo.dirstate.p1():
149 if ctx.node() != repo.dirstate.p1():
150 hg.clean(repo, ctx.node(), show_stats=False)
150 hg.clean(repo, ctx.node(), show_stats=False)
151 with repo.dirstate.parentchange():
151 with repo.dirstate.parentchange():
152 scmutil.movedirstate(repo, ctx.p1())
152 scmutil.movedirstate(repo, ctx.p1())
153
153
154 # Any modified, added, removed, deleted result means split is incomplete
154 # Any modified, added, removed, deleted result means split is incomplete
155 incomplete = lambda repo: any(repo.status()[:4])
155 def incomplete(repo):
156 st = repo.status()
157 return any((st.modified, st.added, st.removed, st.deleted))
156
158
157 # Main split loop
159 # Main split loop
158 while incomplete(repo):
160 while incomplete(repo):
159 if committed:
161 if committed:
160 header = _(
162 header = _(
161 b'HG: Splitting %s. So far it has been split into:\n'
163 b'HG: Splitting %s. So far it has been split into:\n'
162 ) % short(ctx.node())
164 ) % short(ctx.node())
163 for c in committed:
165 for c in committed:
164 firstline = c.description().split(b'\n', 1)[0]
166 firstline = c.description().split(b'\n', 1)[0]
165 header += _(b'HG: - %s: %s\n') % (short(c.node()), firstline)
167 header += _(b'HG: - %s: %s\n') % (short(c.node()), firstline)
166 header += _(
168 header += _(
167 b'HG: Write commit message for the next split changeset.\n'
169 b'HG: Write commit message for the next split changeset.\n'
168 )
170 )
169 else:
171 else:
170 header = _(
172 header = _(
171 b'HG: Splitting %s. Write commit message for the '
173 b'HG: Splitting %s. Write commit message for the '
172 b'first split changeset.\n'
174 b'first split changeset.\n'
173 ) % short(ctx.node())
175 ) % short(ctx.node())
174 opts.update(
176 opts.update(
175 {
177 {
176 b'edit': True,
178 b'edit': True,
177 b'interactive': True,
179 b'interactive': True,
178 b'message': header + ctx.description(),
180 b'message': header + ctx.description(),
179 }
181 }
180 )
182 )
181 commands.commit(ui, repo, **pycompat.strkwargs(opts))
183 commands.commit(ui, repo, **pycompat.strkwargs(opts))
182 newctx = repo[b'.']
184 newctx = repo[b'.']
183 committed.append(newctx)
185 committed.append(newctx)
184
186
185 if not committed:
187 if not committed:
186 raise error.Abort(_(b'cannot split an empty revision'))
188 raise error.Abort(_(b'cannot split an empty revision'))
187
189
188 scmutil.cleanupnodes(
190 scmutil.cleanupnodes(
189 repo,
191 repo,
190 {ctx.node(): [c.node() for c in committed]},
192 {ctx.node(): [c.node() for c in committed]},
191 operation=b'split',
193 operation=b'split',
192 fixphase=True,
194 fixphase=True,
193 )
195 )
194
196
195 return committed[-1]
197 return committed[-1]
196
198
197
199
198 def dorebase(ui, repo, src, destctx):
200 def dorebase(ui, repo, src, destctx):
199 rebase.rebase(
201 rebase.rebase(
200 ui,
202 ui,
201 repo,
203 repo,
202 rev=[revsetlang.formatspec(b'%ld', src)],
204 rev=[revsetlang.formatspec(b'%ld', src)],
203 dest=revsetlang.formatspec(b'%d', destctx.rev()),
205 dest=revsetlang.formatspec(b'%d', destctx.rev()),
204 )
206 )
General Comments 0
You need to be logged in to leave comments. Login now