##// END OF EJS Templates
split: use the new movedirstate() we now have in scmutil...
Martin von Zweigbergk -
r42132:42e2c7c5 default
parent child Browse files
Show More
@@ -1,180 +1,177
1 1 # split.py - split a changeset into smaller ones
2 2 #
3 3 # Copyright 2015 Laurent Charignon <lcharignon@fb.com>
4 4 # Copyright 2017 Facebook, Inc.
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8 """command to split a changeset into smaller ones (EXPERIMENTAL)"""
9 9
10 10 from __future__ import absolute_import
11 11
12 12 from mercurial.i18n import _
13 13
14 14 from mercurial.node import (
15 15 nullid,
16 16 short,
17 17 )
18 18
19 19 from mercurial import (
20 20 bookmarks,
21 21 cmdutil,
22 22 commands,
23 23 error,
24 24 hg,
25 25 obsolete,
26 26 phases,
27 27 pycompat,
28 28 registrar,
29 29 revsetlang,
30 30 scmutil,
31 31 )
32 32
33 33 # allow people to use split without explicitly enabling rebase extension
34 34 from . import (
35 35 rebase,
36 36 )
37 37
38 38 cmdtable = {}
39 39 command = registrar.command(cmdtable)
40 40
41 41 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
42 42 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
43 43 # be specifying the version(s) of Mercurial they are tested with, or
44 44 # leave the attribute unspecified.
45 45 testedwith = 'ships-with-hg-core'
46 46
47 47 @command('split',
48 48 [('r', 'rev', '', _("revision to split"), _('REV')),
49 49 ('', 'rebase', True, _('rebase descendants after split')),
50 50 ] + cmdutil.commitopts2,
51 51 _('hg split [--no-rebase] [[-r] REV]'),
52 52 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, helpbasic=True)
53 53 def split(ui, repo, *revs, **opts):
54 54 """split a changeset into smaller ones
55 55
56 56 Repeatedly prompt changes and commit message for new changesets until there
57 57 is nothing left in the original changeset.
58 58
59 59 If --rev was not given, split the working directory parent.
60 60
61 61 By default, rebase connected non-obsoleted descendants onto the new
62 62 changeset. Use --no-rebase to avoid the rebase.
63 63 """
64 64 opts = pycompat.byteskwargs(opts)
65 65 revlist = []
66 66 if opts.get('rev'):
67 67 revlist.append(opts.get('rev'))
68 68 revlist.extend(revs)
69 69 with repo.wlock(), repo.lock(), repo.transaction('split') as tr:
70 70 revs = scmutil.revrange(repo, revlist or ['.'])
71 71 if len(revs) > 1:
72 72 raise error.Abort(_('cannot split multiple revisions'))
73 73
74 74 rev = revs.first()
75 75 ctx = repo[rev]
76 76 if rev is None or ctx.node() == nullid:
77 77 ui.status(_('nothing to split\n'))
78 78 return 1
79 79 if ctx.node() is None:
80 80 raise error.Abort(_('cannot split working directory'))
81 81
82 82 # rewriteutil.precheck is not very useful here because:
83 83 # 1. null check is done above and it's more friendly to return 1
84 84 # instead of abort
85 85 # 2. mergestate check is done below by cmdutil.bailifchanged
86 86 # 3. unstable check is more complex here because of --rebase
87 87 #
88 88 # So only "public" check is useful and it's checked directly here.
89 89 if ctx.phase() == phases.public:
90 90 raise error.Abort(_('cannot split public changeset'),
91 91 hint=_("see 'hg help phases' for details"))
92 92
93 93 descendants = list(repo.revs('(%d::) - (%d)', rev, rev))
94 94 alloworphaned = obsolete.isenabled(repo, obsolete.allowunstableopt)
95 95 if opts.get('rebase'):
96 96 # Skip obsoleted descendants and their descendants so the rebase
97 97 # won't cause conflicts for sure.
98 98 torebase = list(repo.revs('%ld - (%ld & obsolete())::',
99 99 descendants, descendants))
100 100 if not alloworphaned and len(torebase) != len(descendants):
101 101 raise error.Abort(_('split would leave orphaned changesets '
102 102 'behind'))
103 103 else:
104 104 if not alloworphaned and descendants:
105 105 raise error.Abort(
106 106 _('cannot split changeset with children without rebase'))
107 107 torebase = ()
108 108
109 109 if len(ctx.parents()) > 1:
110 110 raise error.Abort(_('cannot split a merge changeset'))
111 111
112 112 cmdutil.bailifchanged(repo)
113 113
114 114 # Deactivate bookmark temporarily so it won't get moved unintentionally
115 115 bname = repo._activebookmark
116 116 if bname and repo._bookmarks[bname] != ctx.node():
117 117 bookmarks.deactivate(repo)
118 118
119 119 wnode = repo['.'].node()
120 120 top = None
121 121 try:
122 122 top = dosplit(ui, repo, tr, ctx, opts)
123 123 finally:
124 124 # top is None: split failed, need update --clean recovery.
125 125 # wnode == ctx.node(): wnode split, no need to update.
126 126 if top is None or wnode != ctx.node():
127 127 hg.clean(repo, wnode, show_stats=False)
128 128 if bname:
129 129 bookmarks.activate(repo, bname)
130 130 if torebase and top:
131 131 dorebase(ui, repo, torebase, top)
132 132
133 133 def dosplit(ui, repo, tr, ctx, opts):
134 134 committed = [] # [ctx]
135 135
136 136 # Set working parent to ctx.p1(), and keep working copy as ctx's content
137 # NOTE: if we can have "update without touching working copy" API, the
138 # revert step could be cheaper.
139 hg.clean(repo, ctx.p1().node(), show_stats=False)
140 parents = repo.changelog.parents(ctx.node())
141 ui.pushbuffer()
142 cmdutil.revert(ui, repo, ctx, parents)
143 ui.popbuffer() # discard "reverting ..." messages
137 if ctx.node() != repo.dirstate.p1():
138 hg.clean(repo, ctx.node(), show_stats=False)
139 with repo.dirstate.parentchange():
140 scmutil.movedirstate(repo, ctx.p1())
144 141
145 142 # Any modified, added, removed, deleted result means split is incomplete
146 143 incomplete = lambda repo: any(repo.status()[:4])
147 144
148 145 # Main split loop
149 146 while incomplete(repo):
150 147 if committed:
151 148 header = (_('HG: Splitting %s. So far it has been split into:\n')
152 149 % short(ctx.node()))
153 150 for c in committed:
154 151 firstline = c.description().split('\n', 1)[0]
155 152 header += _('HG: - %s: %s\n') % (short(c.node()), firstline)
156 153 header += _('HG: Write commit message for the next split '
157 154 'changeset.\n')
158 155 else:
159 156 header = _('HG: Splitting %s. Write commit message for the '
160 157 'first split changeset.\n') % short(ctx.node())
161 158 opts.update({
162 159 'edit': True,
163 160 'interactive': True,
164 161 'message': header + ctx.description(),
165 162 })
166 163 commands.commit(ui, repo, **pycompat.strkwargs(opts))
167 164 newctx = repo['.']
168 165 committed.append(newctx)
169 166
170 167 if not committed:
171 168 raise error.Abort(_('cannot split an empty revision'))
172 169
173 170 scmutil.cleanupnodes(repo, {ctx.node(): [c.node() for c in committed]},
174 171 operation='split', fixphase=True)
175 172
176 173 return committed[-1]
177 174
178 175 def dorebase(ui, repo, src, destctx):
179 176 rebase.rebase(ui, repo, rev=[revsetlang.formatspec('%ld', src)],
180 177 dest=revsetlang.formatspec('%d', destctx.rev()))
@@ -1,355 +1,267
1 1 Tests for experimental.removeemptydirs
2 2
3 3 $ NO_RM=--config=experimental.removeemptydirs=0
4 4 $ isdir() { if [ -d $1 ]; then echo yes; else echo no; fi }
5 5 $ isfile() { if [ -f $1 ]; then echo yes; else echo no; fi }
6 6
7 7 `hg rm` of the last file in a directory:
8 8 $ hg init hgrm
9 9 $ cd hgrm
10 10 $ mkdir somedir
11 11 $ echo hi > somedir/foo
12 12 $ hg ci -qAm foo
13 13 $ isdir somedir
14 14 yes
15 15 $ hg rm somedir/foo
16 16 $ isdir somedir
17 17 no
18 18 $ hg revert -qa
19 19 $ isdir somedir
20 20 yes
21 21 $ hg $NO_RM rm somedir/foo
22 22 $ isdir somedir
23 23 yes
24 24 $ ls somedir
25 25 $ cd $TESTTMP
26 26
27 27 `hg mv` of the last file in a directory:
28 28 $ hg init hgmv
29 29 $ cd hgmv
30 30 $ mkdir somedir
31 31 $ mkdir destdir
32 32 $ echo hi > somedir/foo
33 33 $ hg ci -qAm foo
34 34 $ isdir somedir
35 35 yes
36 36 $ hg mv somedir/foo destdir/foo
37 37 $ isdir somedir
38 38 no
39 39 $ hg revert -qa
40 40 (revert doesn't get rid of destdir/foo?)
41 41 $ rm destdir/foo
42 42 $ isdir somedir
43 43 yes
44 44 $ hg $NO_RM mv somedir/foo destdir/foo
45 45 $ isdir somedir
46 46 yes
47 47 $ ls somedir
48 48 $ cd $TESTTMP
49 49
50 50 Updating to a commit that doesn't have the directory:
51 51 $ hg init hgupdate
52 52 $ cd hgupdate
53 53 $ echo hi > r0
54 54 $ hg ci -qAm r0
55 55 $ mkdir somedir
56 56 $ echo hi > somedir/foo
57 57 $ hg ci -qAm r1
58 58 $ isdir somedir
59 59 yes
60 60 $ hg co -q -r ".^"
61 61 $ isdir somedir
62 62 no
63 63 $ hg co -q tip
64 64 $ isdir somedir
65 65 yes
66 66 $ hg $NO_RM co -q -r ".^"
67 67 $ isdir somedir
68 68 yes
69 69 $ ls somedir
70 70 $ cd $TESTTMP
71 71
72 72 Rebasing across a commit that doesn't have the directory, from inside the
73 73 directory:
74 74 $ hg init hgrebase
75 75 $ cd hgrebase
76 76 $ echo hi > r0
77 77 $ hg ci -qAm r0
78 78 $ mkdir somedir
79 79 $ echo hi > somedir/foo
80 80 $ hg ci -qAm first_rebase_source
81 81 $ hg $NO_RM co -q -r ".^"
82 82 $ echo hi > somedir/bar
83 83 $ hg ci -qAm first_rebase_dest
84 84 $ hg $NO_RM co -q -r ".^"
85 85 $ echo hi > somedir/baz
86 86 $ hg ci -qAm second_rebase_dest
87 87 $ hg co -qr 'desc(first_rebase_source)'
88 88 $ cd $TESTTMP/hgrebase/somedir
89 89 $ hg --config extensions.rebase= rebase -qr . -d 'desc(first_rebase_dest)'
90 90 current directory was removed (rmcwd !)
91 91 (consider changing to repo root: $TESTTMP/hgrebase) (rmcwd !)
92 92 $ cd $TESTTMP/hgrebase/somedir
93 93 (The current node is the rebased first_rebase_source on top of
94 94 first_rebase_dest)
95 95 This should not output anything about current directory being removed:
96 96 $ hg $NO_RM --config extensions.rebase= rebase -qr . -d 'desc(second_rebase_dest)'
97 97 $ cd $TESTTMP
98 98
99 99 Histediting across a commit that doesn't have the directory, from inside the
100 100 directory (reordering nodes):
101 101 $ hg init hghistedit
102 102 $ cd hghistedit
103 103 $ echo hi > r0
104 104 $ hg ci -qAm r0
105 105 $ echo hi > r1
106 106 $ hg ci -qAm r1
107 107 $ echo hi > r2
108 108 $ hg ci -qAm r2
109 109 $ mkdir somedir
110 110 $ echo hi > somedir/foo
111 111 $ hg ci -qAm migrating_revision
112 112 $ cat > histedit_commands <<EOF
113 113 > pick 89079fab8aee 0 r0
114 114 > pick e6d271df3142 1 r1
115 115 > pick 89e25aa83f0f 3 migrating_revision
116 116 > pick b550aa12d873 2 r2
117 117 > EOF
118 118 $ cd $TESTTMP/hghistedit/somedir
119 119 $ hg --config extensions.histedit= histedit -q --commands ../histedit_commands
120 120
121 121 histedit doesn't output anything when the current diretory is removed. We rely
122 122 on the tests being commonly run on machines where the current directory
123 123 disappearing from underneath us actually has an observable effect, such as an
124 124 error or no files listed
125 125 #if linuxormacos
126 126 $ isfile foo
127 127 no
128 128 #endif
129 129 $ cd $TESTTMP/hghistedit/somedir
130 130 $ isfile foo
131 131 yes
132 132
133 133 $ cd $TESTTMP/hghistedit
134 134 $ cat > histedit_commands <<EOF
135 135 > pick 89079fab8aee 0 r0
136 136 > pick 7c7a22c6009f 3 migrating_revision
137 137 > pick e6d271df3142 1 r1
138 138 > pick 40a53c2d4276 2 r2
139 139 > EOF
140 140 $ cd $TESTTMP/hghistedit/somedir
141 141 $ hg $NO_RM --config extensions.histedit= histedit -q --commands ../histedit_commands
142 142 Regardless of system, we should always get a 'yes' here.
143 143 $ isfile foo
144 144 yes
145 145 $ cd $TESTTMP
146 146
147 147 This is essentially the exact test from issue5826, just cleaned up a little:
148 148
149 149 $ hg init issue5826_withrm
150 150 $ cd issue5826_withrm
151 151
152 152 Let's only turn this on for this repo so that we don't contaminate later tests.
153 153 $ cat >> .hg/hgrc <<EOF
154 154 > [extensions]
155 155 > histedit =
156 156 > EOF
157 157 Commit three revisions that each create a directory:
158 158
159 159 $ mkdir foo
160 160 $ touch foo/bar
161 161 $ hg commit -qAm "add foo"
162 162
163 163 $ mkdir bar
164 164 $ touch bar/bar
165 165 $ hg commit -qAm "add bar"
166 166
167 167 $ mkdir baz
168 168 $ touch baz/bar
169 169 $ hg commit -qAm "add baz"
170 170
171 171 Enter the first directory:
172 172
173 173 $ cd foo
174 174
175 175 Histedit doing 'pick, pick, fold':
176 176
177 177 #if rmcwd
178 178
179 179 $ hg histedit --commands - <<EOF
180 180 > pick 6274c77c93c3 1 add bar
181 181 > pick ff70a87b588f 0 add foo
182 182 > fold 9992bb0ac0db 2 add baz
183 183 > EOF
184 184 abort: $ENOENT$
185 185 [255]
186 186
187 187 Go back to the repo root after losing it as part of that operation:
188 188 $ cd $TESTTMP/issue5826_withrm
189 189
190 190 Note the lack of a non-zero exit code from this function - it exits
191 191 successfully, but doesn't really do anything.
192 192 $ hg histedit --continue
193 193 9992bb0ac0db: cannot fold - working copy is not a descendant of previous commit 5c806432464a
194 194 saved backup bundle to $TESTTMP/issue5826_withrm/.hg/strip-backup/ff70a87b588f-e94f9789-histedit.hg
195 195
196 196 $ hg log -T '{rev}:{node|short} {desc}\n'
197 197 2:94e3f9fae1d6 fold-temp-revision 9992bb0ac0db
198 198 1:5c806432464a add foo
199 199 0:d17db4b0303a add bar
200 200
201 201 #else
202 202
203 203 $ cd $TESTTMP/issue5826_withrm
204 204
205 205 $ hg histedit --commands - <<EOF
206 206 > pick 6274c77c93c3 1 add bar
207 207 > pick ff70a87b588f 0 add foo
208 208 > fold 9992bb0ac0db 2 add baz
209 209 > EOF
210 210 saved backup bundle to $TESTTMP/issue5826_withrm/.hg/strip-backup/5c806432464a-cd4c8d86-histedit.hg
211 211
212 212 $ hg log -T '{rev}:{node|short} {desc}\n'
213 213 1:b9eddaa97cbc add foo
214 214 ***
215 215 add baz
216 216 0:d17db4b0303a add bar
217 217
218 218 #endif
219 219
220 220 Now test that again with experimental.removeemptydirs=false:
221 221 $ hg init issue5826_norm
222 222 $ cd issue5826_norm
223 223
224 224 Let's only turn this on for this repo so that we don't contaminate later tests.
225 225 $ cat >> .hg/hgrc <<EOF
226 226 > [extensions]
227 227 > histedit =
228 228 > [experimental]
229 229 > removeemptydirs = false
230 230 > EOF
231 231 Commit three revisions that each create a directory:
232 232
233 233 $ mkdir foo
234 234 $ touch foo/bar
235 235 $ hg commit -qAm "add foo"
236 236
237 237 $ mkdir bar
238 238 $ touch bar/bar
239 239 $ hg commit -qAm "add bar"
240 240
241 241 $ mkdir baz
242 242 $ touch baz/bar
243 243 $ hg commit -qAm "add baz"
244 244
245 245 Enter the first directory:
246 246
247 247 $ cd foo
248 248
249 249 Histedit doing 'pick, pick, fold':
250 250
251 251 $ hg histedit --commands - <<EOF
252 252 > pick 6274c77c93c3 1 add bar
253 253 > pick ff70a87b588f 0 add foo
254 254 > fold 9992bb0ac0db 2 add baz
255 255 > EOF
256 256 saved backup bundle to $TESTTMP/issue5826_withrm/issue5826_norm/.hg/strip-backup/5c806432464a-cd4c8d86-histedit.hg
257 257
258 258 Note the lack of a 'cd' being necessary here, and we don't need to 'histedit
259 259 --continue'
260 260
261 261 $ hg log -T '{rev}:{node|short} {desc}\n'
262 262 1:b9eddaa97cbc add foo
263 263 ***
264 264 add baz
265 265 0:d17db4b0303a add bar
266 266
267 267 $ cd $TESTTMP
268
269 Testing `hg split` being run from inside of a directory that was created in the
270 commit being split:
271
272 $ hg init hgsplit
273 $ cd hgsplit
274 $ cat >> .hg/hgrc << EOF
275 > [ui]
276 > interactive = 1
277 > [extensions]
278 > split =
279 > EOF
280 $ echo anchor > anchor.txt
281 $ hg ci -qAm anchor
282
283 Create a changeset with '/otherfile_in_root' and 'somedir/foo', then try to
284 split it.
285 $ echo otherfile > otherfile_in_root
286 $ mkdir somedir
287 $ cd somedir
288 $ echo hi > foo
289 $ hg ci -qAm split_me
290 (Note: need to make this file not in this directory, or else the bug doesn't
291 reproduce; we're using a separate file due to concerns of portability on
292 `echo -e`)
293 $ cat > ../split_commands << EOF
294 > n
295 > y
296 > y
297 > a
298 > EOF
299
300 The split succeeds on no-rmcwd platforms, which alters the rest of the tests
301 #if rmcwd
302 $ cat ../split_commands | hg split
303 current directory was removed
304 (consider changing to repo root: $TESTTMP/hgsplit)
305 diff --git a/otherfile_in_root b/otherfile_in_root
306 new file mode 100644
307 examine changes to 'otherfile_in_root'? [Ynesfdaq?] n
308
309 diff --git a/somedir/foo b/somedir/foo
310 new file mode 100644
311 examine changes to 'somedir/foo'? [Ynesfdaq?] y
312
313 @@ -0,0 +1,1 @@
314 +hi
315 record change 2/2 to 'somedir/foo'? [Ynesfdaq?] y
316
317 abort: $ENOENT$
318 [255]
319 #endif
320
321 Let's try that again without the rmdir
322 $ cd $TESTTMP/hgsplit/somedir
323 Show that the previous split didn't do anything
324 $ hg log -T '{rev}:{node|short} {desc}\n'
325 1:e26b22a4f0b7 split_me
326 0:7e53273730c0 anchor
327 $ hg status
328 ? split_commands
329 Try again
330 $ cat ../split_commands | hg $NO_RM split
331 diff --git a/otherfile_in_root b/otherfile_in_root
332 new file mode 100644
333 examine changes to 'otherfile_in_root'? [Ynesfdaq?] n
334
335 diff --git a/somedir/foo b/somedir/foo
336 new file mode 100644
337 examine changes to 'somedir/foo'? [Ynesfdaq?] y
338
339 @@ -0,0 +1,1 @@
340 +hi
341 record change 2/2 to 'somedir/foo'? [Ynesfdaq?] y
342
343 created new head
344 diff --git a/otherfile_in_root b/otherfile_in_root
345 new file mode 100644
346 examine changes to 'otherfile_in_root'? [Ynesfdaq?] a
347
348 saved backup bundle to $TESTTMP/hgsplit/.hg/strip-backup/*-split.hg (glob)
349 Show that this split did something
350 $ hg log -T '{rev}:{node|short} {desc}\n'
351 2:a440f24fca4f split_me
352 1:c994f20276ab split_me
353 0:7e53273730c0 anchor
354 $ hg status
355 ? split_commands
General Comments 0
You need to be logged in to leave comments. Login now