##// END OF EJS Templates
py3: fix kwargs handling in hgext/split.py...
Pulkit Goyal -
r38080:5ba0cf22 default
parent child Browse files
Show More
@@ -1,178 +1,179 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 (
34 from . import (
35 rebase,
35 rebase,
36 )
36 )
37
37
38 cmdtable = {}
38 cmdtable = {}
39 command = registrar.command(cmdtable)
39 command = registrar.command(cmdtable)
40
40
41 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
41 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
42 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
42 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
43 # be specifying the version(s) of Mercurial they are tested with, or
43 # be specifying the version(s) of Mercurial they are tested with, or
44 # leave the attribute unspecified.
44 # leave the attribute unspecified.
45 testedwith = 'ships-with-hg-core'
45 testedwith = 'ships-with-hg-core'
46
46
47 @command('^split',
47 @command('^split',
48 [('r', 'rev', '', _("revision to split"), _('REV')),
48 [('r', 'rev', '', _("revision to split"), _('REV')),
49 ('', 'rebase', True, _('rebase descendants after split')),
49 ('', 'rebase', True, _('rebase descendants after split')),
50 ] + cmdutil.commitopts2,
50 ] + cmdutil.commitopts2,
51 _('hg split [--no-rebase] [[-r] REV]'))
51 _('hg split [--no-rebase] [[-r] REV]'))
52 def split(ui, repo, *revs, **opts):
52 def split(ui, repo, *revs, **opts):
53 """split a changeset into smaller ones
53 """split a changeset into smaller ones
54
54
55 Repeatedly prompt changes and commit message for new changesets until there
55 Repeatedly prompt changes and commit message for new changesets until there
56 is nothing left in the original changeset.
56 is nothing left in the original changeset.
57
57
58 If --rev was not given, split the working directory parent.
58 If --rev was not given, split the working directory parent.
59
59
60 By default, rebase connected non-obsoleted descendants onto the new
60 By default, rebase connected non-obsoleted descendants onto the new
61 changeset. Use --no-rebase to avoid the rebase.
61 changeset. Use --no-rebase to avoid the rebase.
62 """
62 """
63 opts = pycompat.byteskwargs(opts)
63 revlist = []
64 revlist = []
64 if opts.get('rev'):
65 if opts.get('rev'):
65 revlist.append(opts.get('rev'))
66 revlist.append(opts.get('rev'))
66 revlist.extend(revs)
67 revlist.extend(revs)
67 with repo.wlock(), repo.lock(), repo.transaction('split') as tr:
68 with repo.wlock(), repo.lock(), repo.transaction('split') as tr:
68 revs = scmutil.revrange(repo, revlist or ['.'])
69 revs = scmutil.revrange(repo, revlist or ['.'])
69 if len(revs) > 1:
70 if len(revs) > 1:
70 raise error.Abort(_('cannot split multiple revisions'))
71 raise error.Abort(_('cannot split multiple revisions'))
71
72
72 rev = revs.first()
73 rev = revs.first()
73 ctx = repo[rev]
74 ctx = repo[rev]
74 if rev is None or ctx.node() == nullid:
75 if rev is None or ctx.node() == nullid:
75 ui.status(_('nothing to split\n'))
76 ui.status(_('nothing to split\n'))
76 return 1
77 return 1
77 if ctx.node() is None:
78 if ctx.node() is None:
78 raise error.Abort(_('cannot split working directory'))
79 raise error.Abort(_('cannot split working directory'))
79
80
80 # rewriteutil.precheck is not very useful here because:
81 # rewriteutil.precheck is not very useful here because:
81 # 1. null check is done above and it's more friendly to return 1
82 # 1. null check is done above and it's more friendly to return 1
82 # instead of abort
83 # instead of abort
83 # 2. mergestate check is done below by cmdutil.bailifchanged
84 # 2. mergestate check is done below by cmdutil.bailifchanged
84 # 3. unstable check is more complex here because of --rebase
85 # 3. unstable check is more complex here because of --rebase
85 #
86 #
86 # So only "public" check is useful and it's checked directly here.
87 # So only "public" check is useful and it's checked directly here.
87 if ctx.phase() == phases.public:
88 if ctx.phase() == phases.public:
88 raise error.Abort(_('cannot split public changeset'),
89 raise error.Abort(_('cannot split public changeset'),
89 hint=_("see 'hg help phases' for details"))
90 hint=_("see 'hg help phases' for details"))
90
91
91 descendants = list(repo.revs('(%d::) - (%d)', rev, rev))
92 descendants = list(repo.revs('(%d::) - (%d)', rev, rev))
92 alloworphaned = obsolete.isenabled(repo, obsolete.allowunstableopt)
93 alloworphaned = obsolete.isenabled(repo, obsolete.allowunstableopt)
93 if opts.get('rebase'):
94 if opts.get('rebase'):
94 # Skip obsoleted descendants and their descendants so the rebase
95 # Skip obsoleted descendants and their descendants so the rebase
95 # won't cause conflicts for sure.
96 # won't cause conflicts for sure.
96 torebase = list(repo.revs('%ld - (%ld & obsolete())::',
97 torebase = list(repo.revs('%ld - (%ld & obsolete())::',
97 descendants, descendants))
98 descendants, descendants))
98 if not alloworphaned and len(torebase) != len(descendants):
99 if not alloworphaned and len(torebase) != len(descendants):
99 raise error.Abort(_('split would leave orphaned changesets '
100 raise error.Abort(_('split would leave orphaned changesets '
100 'behind'))
101 'behind'))
101 else:
102 else:
102 if not alloworphaned and descendants:
103 if not alloworphaned and descendants:
103 raise error.Abort(
104 raise error.Abort(
104 _('cannot split changeset with children without rebase'))
105 _('cannot split changeset with children without rebase'))
105 torebase = ()
106 torebase = ()
106
107
107 if len(ctx.parents()) > 1:
108 if len(ctx.parents()) > 1:
108 raise error.Abort(_('cannot split a merge changeset'))
109 raise error.Abort(_('cannot split a merge changeset'))
109
110
110 cmdutil.bailifchanged(repo)
111 cmdutil.bailifchanged(repo)
111
112
112 # Deactivate bookmark temporarily so it won't get moved unintentionally
113 # Deactivate bookmark temporarily so it won't get moved unintentionally
113 bname = repo._activebookmark
114 bname = repo._activebookmark
114 if bname and repo._bookmarks[bname] != ctx.node():
115 if bname and repo._bookmarks[bname] != ctx.node():
115 bookmarks.deactivate(repo)
116 bookmarks.deactivate(repo)
116
117
117 wnode = repo['.'].node()
118 wnode = repo['.'].node()
118 top = None
119 top = None
119 try:
120 try:
120 top = dosplit(ui, repo, tr, ctx, opts)
121 top = dosplit(ui, repo, tr, ctx, opts)
121 finally:
122 finally:
122 # top is None: split failed, need update --clean recovery.
123 # top is None: split failed, need update --clean recovery.
123 # wnode == ctx.node(): wnode split, no need to update.
124 # wnode == ctx.node(): wnode split, no need to update.
124 if top is None or wnode != ctx.node():
125 if top is None or wnode != ctx.node():
125 hg.clean(repo, wnode, show_stats=False)
126 hg.clean(repo, wnode, show_stats=False)
126 if bname:
127 if bname:
127 bookmarks.activate(repo, bname)
128 bookmarks.activate(repo, bname)
128 if torebase and top:
129 if torebase and top:
129 dorebase(ui, repo, torebase, top)
130 dorebase(ui, repo, torebase, top)
130
131
131 def dosplit(ui, repo, tr, ctx, opts):
132 def dosplit(ui, repo, tr, ctx, opts):
132 committed = [] # [ctx]
133 committed = [] # [ctx]
133
134
134 # Set working parent to ctx.p1(), and keep working copy as ctx's content
135 # Set working parent to ctx.p1(), and keep working copy as ctx's content
135 # NOTE: if we can have "update without touching working copy" API, the
136 # NOTE: if we can have "update without touching working copy" API, the
136 # revert step could be cheaper.
137 # revert step could be cheaper.
137 hg.clean(repo, ctx.p1().node(), show_stats=False)
138 hg.clean(repo, ctx.p1().node(), show_stats=False)
138 parents = repo.changelog.parents(ctx.node())
139 parents = repo.changelog.parents(ctx.node())
139 ui.pushbuffer()
140 ui.pushbuffer()
140 cmdutil.revert(ui, repo, ctx, parents)
141 cmdutil.revert(ui, repo, ctx, parents)
141 ui.popbuffer() # discard "reverting ..." messages
142 ui.popbuffer() # discard "reverting ..." messages
142
143
143 # Any modified, added, removed, deleted result means split is incomplete
144 # Any modified, added, removed, deleted result means split is incomplete
144 incomplete = lambda repo: any(repo.status()[:4])
145 incomplete = lambda repo: any(repo.status()[:4])
145
146
146 # Main split loop
147 # Main split loop
147 while incomplete(repo):
148 while incomplete(repo):
148 if committed:
149 if committed:
149 header = (_('HG: Splitting %s. So far it has been split into:\n')
150 header = (_('HG: Splitting %s. So far it has been split into:\n')
150 % short(ctx.node()))
151 % short(ctx.node()))
151 for c in committed:
152 for c in committed:
152 firstline = c.description().split('\n', 1)[0]
153 firstline = c.description().split('\n', 1)[0]
153 header += _('HG: - %s: %s\n') % (short(c.node()), firstline)
154 header += _('HG: - %s: %s\n') % (short(c.node()), firstline)
154 header += _('HG: Write commit message for the next split '
155 header += _('HG: Write commit message for the next split '
155 'changeset.\n')
156 'changeset.\n')
156 else:
157 else:
157 header = _('HG: Splitting %s. Write commit message for the '
158 header = _('HG: Splitting %s. Write commit message for the '
158 'first split changeset.\n') % short(ctx.node())
159 'first split changeset.\n') % short(ctx.node())
159 opts.update({
160 opts.update({
160 'edit': True,
161 'edit': True,
161 'interactive': True,
162 'interactive': True,
162 'message': header + ctx.description(),
163 'message': header + ctx.description(),
163 })
164 })
164 commands.commit(ui, repo, **pycompat.strkwargs(opts))
165 commands.commit(ui, repo, **pycompat.strkwargs(opts))
165 newctx = repo['.']
166 newctx = repo['.']
166 committed.append(newctx)
167 committed.append(newctx)
167
168
168 if not committed:
169 if not committed:
169 raise error.Abort(_('cannot split an empty revision'))
170 raise error.Abort(_('cannot split an empty revision'))
170
171
171 scmutil.cleanupnodes(repo, {ctx.node(): [c.node() for c in committed]},
172 scmutil.cleanupnodes(repo, {ctx.node(): [c.node() for c in committed]},
172 operation='split')
173 operation='split')
173
174
174 return committed[-1]
175 return committed[-1]
175
176
176 def dorebase(ui, repo, src, destctx):
177 def dorebase(ui, repo, src, destctx):
177 rebase.rebase(ui, repo, rev=[revsetlang.formatspec('%ld', src)],
178 rebase.rebase(ui, repo, rev=[revsetlang.formatspec('%ld', src)],
178 dest=revsetlang.formatspec('%d', destctx.rev()))
179 dest=revsetlang.formatspec('%d', destctx.rev()))
General Comments 0
You need to be logged in to leave comments. Login now