##// END OF EJS Templates
uncommit: rename the flag 'empty' to 'keep' which retains empty changeset...
Pulkit Goyal -
r34284:f94442d4 default
parent child Browse files
Show More
@@ -1,183 +1,183 b''
1 1 # uncommit - undo the actions of a commit
2 2 #
3 3 # Copyright 2011 Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
4 4 # Logilab SA <contact@logilab.fr>
5 5 # Pierre-Yves David <pierre-yves.david@ens-lyon.org>
6 6 # Patrick Mezard <patrick@mezard.eu>
7 7 # Copyright 2016 Facebook, Inc.
8 8 #
9 9 # This software may be used and distributed according to the terms of the
10 10 # GNU General Public License version 2 or any later version.
11 11
12 12 """uncommit part or all of a local changeset (EXPERIMENTAL)
13 13
14 14 This command undoes the effect of a local commit, returning the affected
15 15 files to their uncommitted state. This means that files modified, added or
16 16 removed in the changeset will be left unchanged, and so will remain modified,
17 17 added and removed in the working directory.
18 18 """
19 19
20 20 from __future__ import absolute_import
21 21
22 22 from mercurial.i18n import _
23 23
24 24 from mercurial import (
25 25 commands,
26 26 context,
27 27 copies,
28 28 error,
29 29 node,
30 30 obsolete,
31 31 registrar,
32 32 scmutil,
33 33 )
34 34
35 35 cmdtable = {}
36 36 command = registrar.command(cmdtable)
37 37
38 38 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
39 39 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
40 40 # be specifying the version(s) of Mercurial they are tested with, or
41 41 # leave the attribute unspecified.
42 42 testedwith = 'ships-with-hg-core'
43 43
44 44 def _commitfiltered(repo, ctx, match, allowempty):
45 45 """Recommit ctx with changed files not in match. Return the new
46 46 node identifier, or None if nothing changed.
47 47 """
48 48 base = ctx.p1()
49 49 # ctx
50 50 initialfiles = set(ctx.files())
51 51 exclude = set(f for f in initialfiles if match(f))
52 52
53 53 # No files matched commit, so nothing excluded
54 54 if not exclude:
55 55 return None
56 56
57 57 files = (initialfiles - exclude)
58 58 # return the p1 so that we don't create an obsmarker later
59 59 if not files and not allowempty:
60 60 return ctx.parents()[0].node()
61 61
62 62 # Filter copies
63 63 copied = copies.pathcopies(base, ctx)
64 64 copied = dict((dst, src) for dst, src in copied.iteritems()
65 65 if dst in files)
66 66 def filectxfn(repo, memctx, path, contentctx=ctx, redirect=()):
67 67 if path not in contentctx:
68 68 return None
69 69 fctx = contentctx[path]
70 70 mctx = context.memfilectx(repo, fctx.path(), fctx.data(),
71 71 fctx.islink(),
72 72 fctx.isexec(),
73 73 copied=copied.get(path))
74 74 return mctx
75 75
76 76 new = context.memctx(repo,
77 77 parents=[base.node(), node.nullid],
78 78 text=ctx.description(),
79 79 files=files,
80 80 filectxfn=filectxfn,
81 81 user=ctx.user(),
82 82 date=ctx.date(),
83 83 extra=ctx.extra())
84 84 # phase handling
85 85 commitphase = ctx.phase()
86 86 overrides = {('phases', 'new-commit'): commitphase}
87 87 with repo.ui.configoverride(overrides, 'uncommit'):
88 88 newid = repo.commitctx(new)
89 89 return newid
90 90
91 91 def _uncommitdirstate(repo, oldctx, match):
92 92 """Fix the dirstate after switching the working directory from
93 93 oldctx to a copy of oldctx not containing changed files matched by
94 94 match.
95 95 """
96 96 ctx = repo['.']
97 97 ds = repo.dirstate
98 98 copies = dict(ds.copies())
99 99 s = repo.status(oldctx.p1(), oldctx, match=match)
100 100 for f in s.modified:
101 101 if ds[f] == 'r':
102 102 # modified + removed -> removed
103 103 continue
104 104 ds.normallookup(f)
105 105
106 106 for f in s.added:
107 107 if ds[f] == 'r':
108 108 # added + removed -> unknown
109 109 ds.drop(f)
110 110 elif ds[f] != 'a':
111 111 ds.add(f)
112 112
113 113 for f in s.removed:
114 114 if ds[f] == 'a':
115 115 # removed + added -> normal
116 116 ds.normallookup(f)
117 117 elif ds[f] != 'r':
118 118 ds.remove(f)
119 119
120 120 # Merge old parent and old working dir copies
121 121 oldcopies = {}
122 122 for f in (s.modified + s.added):
123 123 src = oldctx[f].renamed()
124 124 if src:
125 125 oldcopies[f] = src[0]
126 126 oldcopies.update(copies)
127 127 copies = dict((dst, oldcopies.get(src, src))
128 128 for dst, src in oldcopies.iteritems())
129 129 # Adjust the dirstate copies
130 130 for dst, src in copies.iteritems():
131 131 if (src not in ctx or dst in ctx or ds[dst] != 'a'):
132 132 src = None
133 133 ds.copy(src, dst)
134 134
135 135 @command('uncommit',
136 [('', 'empty', False, _('allow an empty commit after uncommiting')),
136 [('', 'keep', False, _('allow an empty commit after uncommiting')),
137 137 ] + commands.walkopts,
138 138 _('[OPTION]... [FILE]...'))
139 139 def uncommit(ui, repo, *pats, **opts):
140 140 """uncommit part or all of a local changeset
141 141
142 142 This command undoes the effect of a local commit, returning the affected
143 143 files to their uncommitted state. This means that files modified or
144 144 deleted in the changeset will be left unchanged, and so will remain
145 145 modified in the working directory.
146 146 """
147 147
148 148 with repo.wlock(), repo.lock():
149 149 wctx = repo[None]
150 150
151 151 if wctx.parents()[0].node() == node.nullid:
152 152 raise error.Abort(_("cannot uncommit null changeset"))
153 153 if len(wctx.parents()) > 1:
154 154 raise error.Abort(_("cannot uncommit while merging"))
155 155 old = repo['.']
156 156 if not old.mutable():
157 157 raise error.Abort(_('cannot uncommit public changesets'))
158 158 if len(old.parents()) > 1:
159 159 raise error.Abort(_("cannot uncommit merge changeset"))
160 160 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
161 161 if not allowunstable and old.children():
162 162 raise error.Abort(_('cannot uncommit changeset with children'))
163 163
164 164 with repo.transaction('uncommit'):
165 165 match = scmutil.match(old, pats, opts)
166 newid = _commitfiltered(repo, old, match, opts.get('empty'))
166 newid = _commitfiltered(repo, old, match, opts.get('keep'))
167 167 if newid is None:
168 168 ui.status(_("nothing to uncommit\n"))
169 169 return 1
170 170
171 171 mapping = {}
172 172 if newid != old.p1().node():
173 173 # Move local changes on filtered changeset
174 174 mapping[old.node()] = (newid,)
175 175 else:
176 176 # Fully removed the old commit
177 177 mapping[old.node()] = ()
178 178
179 179 scmutil.cleanupnodes(repo, mapping, 'uncommit')
180 180
181 181 with repo.dirstate.parentchange():
182 182 repo.dirstate.setparents(newid, node.nullid)
183 183 _uncommitdirstate(repo, old, match)
@@ -1,366 +1,366 b''
1 1 Test uncommit - set up the config
2 2
3 3 $ cat >> $HGRCPATH <<EOF
4 4 > [experimental]
5 5 > evolution=createmarkers, allowunstable
6 6 > [extensions]
7 7 > uncommit =
8 8 > drawdag=$TESTDIR/drawdag.py
9 9 > EOF
10 10
11 11 Build up a repo
12 12
13 13 $ hg init repo
14 14 $ cd repo
15 15 $ hg bookmark foo
16 16
17 17 Help for uncommit
18 18
19 19 $ hg help uncommit
20 20 hg uncommit [OPTION]... [FILE]...
21 21
22 22 uncommit part or all of a local changeset
23 23
24 24 This command undoes the effect of a local commit, returning the affected
25 25 files to their uncommitted state. This means that files modified or
26 26 deleted in the changeset will be left unchanged, and so will remain
27 27 modified in the working directory.
28 28
29 29 (use 'hg help -e uncommit' to show help for the uncommit extension)
30 30
31 31 options ([+] can be repeated):
32 32
33 --empty allow an empty commit after uncommiting
33 --keep allow an empty commit after uncommiting
34 34 -I --include PATTERN [+] include names matching the given patterns
35 35 -X --exclude PATTERN [+] exclude names matching the given patterns
36 36
37 37 (some details hidden, use --verbose to show complete help)
38 38
39 39 Uncommit with no commits should fail
40 40
41 41 $ hg uncommit
42 42 abort: cannot uncommit null changeset
43 43 [255]
44 44
45 45 Create some commits
46 46
47 47 $ touch files
48 48 $ hg add files
49 49 $ for i in a ab abc abcd abcde; do echo $i > files; echo $i > file-$i; hg add file-$i; hg commit -m "added file-$i"; done
50 50 $ ls
51 51 file-a
52 52 file-ab
53 53 file-abc
54 54 file-abcd
55 55 file-abcde
56 56 files
57 57
58 58 $ hg log -G -T '{rev}:{node} {desc}' --hidden
59 59 @ 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
60 60 |
61 61 o 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
62 62 |
63 63 o 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
64 64 |
65 65 o 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
66 66 |
67 67 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
68 68
69 69 Simple uncommit off the top, also moves bookmark
70 70
71 71 $ hg bookmark
72 72 * foo 4:6c4fd43ed714
73 73 $ hg uncommit
74 74 $ hg status
75 75 M files
76 76 A file-abcde
77 77 $ hg bookmark
78 78 * foo 3:6db330d65db4
79 79
80 80 $ hg log -G -T '{rev}:{node} {desc}' --hidden
81 81 x 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
82 82 |
83 83 @ 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
84 84 |
85 85 o 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
86 86 |
87 87 o 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
88 88 |
89 89 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
90 90
91 91
92 92 Recommit
93 93
94 94 $ hg commit -m 'new change abcde'
95 95 $ hg status
96 96 $ hg heads -T '{rev}:{node} {desc}'
97 97 5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde (no-eol)
98 98
99 99 Uncommit of non-existent and unchanged files has no effect
100 100 $ hg uncommit nothinghere
101 101 nothing to uncommit
102 102 [1]
103 103 $ hg status
104 104 $ hg uncommit file-abc
105 105 nothing to uncommit
106 106 [1]
107 107 $ hg status
108 108
109 109 Try partial uncommit, also moves bookmark
110 110
111 111 $ hg bookmark
112 112 * foo 5:0c07a3ccda77
113 113 $ hg uncommit files
114 114 $ hg status
115 115 M files
116 116 $ hg bookmark
117 117 * foo 6:3727deee06f7
118 118 $ hg heads -T '{rev}:{node} {desc}'
119 119 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde (no-eol)
120 120 $ hg log -r . -p -T '{rev}:{node} {desc}'
121 121 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcdediff -r 6db330d65db4 -r 3727deee06f7 file-abcde
122 122 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
123 123 +++ b/file-abcde Thu Jan 01 00:00:00 1970 +0000
124 124 @@ -0,0 +1,1 @@
125 125 +abcde
126 126
127 127 $ hg log -G -T '{rev}:{node} {desc}' --hidden
128 128 @ 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde
129 129 |
130 130 | x 5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde
131 131 |/
132 132 | x 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
133 133 |/
134 134 o 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
135 135 |
136 136 o 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
137 137 |
138 138 o 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
139 139 |
140 140 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
141 141
142 142 $ hg commit -m 'update files for abcde'
143 143
144 144 Uncommit with dirty state
145 145
146 146 $ echo "foo" >> files
147 147 $ cat files
148 148 abcde
149 149 foo
150 150 $ hg status
151 151 M files
152 152 $ hg uncommit files
153 153 $ cat files
154 154 abcde
155 155 foo
156 156 $ hg commit -m "files abcde + foo"
157 157
158 158 Uncommit in the middle of a stack, does not move bookmark
159 159
160 160 $ hg checkout '.^^^'
161 161 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
162 162 (leaving bookmark foo)
163 163 $ hg log -r . -p -T '{rev}:{node} {desc}'
164 164 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abcdiff -r 69a232e754b0 -r abf2df566fc1 file-abc
165 165 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
166 166 +++ b/file-abc Thu Jan 01 00:00:00 1970 +0000
167 167 @@ -0,0 +1,1 @@
168 168 +abc
169 169 diff -r 69a232e754b0 -r abf2df566fc1 files
170 170 --- a/files Thu Jan 01 00:00:00 1970 +0000
171 171 +++ b/files Thu Jan 01 00:00:00 1970 +0000
172 172 @@ -1,1 +1,1 @@
173 173 -ab
174 174 +abc
175 175
176 176 $ hg bookmark
177 177 foo 8:83815831694b
178 178 $ hg uncommit
179 179 $ hg status
180 180 M files
181 181 A file-abc
182 182 $ hg heads -T '{rev}:{node} {desc}'
183 183 8:83815831694b1271e9f207cb1b79b2b19275edcb files abcde + foo (no-eol)
184 184 $ hg bookmark
185 185 foo 8:83815831694b
186 186 $ hg commit -m 'new abc'
187 187 created new head
188 188
189 189 Partial uncommit in the middle, does not move bookmark
190 190
191 191 $ hg checkout '.^'
192 192 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
193 193 $ hg log -r . -p -T '{rev}:{node} {desc}'
194 194 1:69a232e754b08d568c4899475faf2eb44b857802 added file-abdiff -r 3004d2d9b508 -r 69a232e754b0 file-ab
195 195 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
196 196 +++ b/file-ab Thu Jan 01 00:00:00 1970 +0000
197 197 @@ -0,0 +1,1 @@
198 198 +ab
199 199 diff -r 3004d2d9b508 -r 69a232e754b0 files
200 200 --- a/files Thu Jan 01 00:00:00 1970 +0000
201 201 +++ b/files Thu Jan 01 00:00:00 1970 +0000
202 202 @@ -1,1 +1,1 @@
203 203 -a
204 204 +ab
205 205
206 206 $ hg bookmark
207 207 foo 8:83815831694b
208 208 $ hg uncommit file-ab
209 209 $ hg status
210 210 A file-ab
211 211
212 212 $ hg heads -T '{rev}:{node} {desc}\n'
213 213 10:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
214 214 9:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
215 215 8:83815831694b1271e9f207cb1b79b2b19275edcb files abcde + foo
216 216
217 217 $ hg bookmark
218 218 foo 8:83815831694b
219 219 $ hg commit -m 'update ab'
220 220 $ hg status
221 221 $ hg heads -T '{rev}:{node} {desc}\n'
222 222 11:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
223 223 9:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
224 224 8:83815831694b1271e9f207cb1b79b2b19275edcb files abcde + foo
225 225
226 226 $ hg log -G -T '{rev}:{node} {desc}' --hidden
227 227 @ 11:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
228 228 |
229 229 o 10:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
230 230 |
231 231 | o 9:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
232 232 | |
233 233 | | o 8:83815831694b1271e9f207cb1b79b2b19275edcb files abcde + foo
234 234 | | |
235 235 | | | x 7:0977fa602c2fd7d8427ed4e7ee15ea13b84c9173 update files for abcde
236 236 | | |/
237 237 | | o 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde
238 238 | | |
239 239 | | | x 5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde
240 240 | | |/
241 241 | | | x 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
242 242 | | |/
243 243 | | o 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
244 244 | | |
245 245 | | x 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
246 246 | |/
247 247 | x 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
248 248 |/
249 249 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
250 250
251 251 Uncommit with draft parent
252 252
253 253 $ hg uncommit
254 254 $ hg phase -r .
255 255 10: draft
256 256 $ hg commit -m 'update ab again'
257 257
258 258 Uncommit with public parent
259 259
260 260 $ hg phase -p "::.^"
261 261 $ hg uncommit
262 262 $ hg phase -r .
263 263 10: public
264 264
265 265 Partial uncommit with public parent
266 266
267 267 $ echo xyz > xyz
268 268 $ hg add xyz
269 269 $ hg commit -m "update ab and add xyz"
270 270 $ hg uncommit xyz
271 271 $ hg status
272 272 A xyz
273 273 $ hg phase -r .
274 274 14: draft
275 275 $ hg phase -r ".^"
276 276 10: public
277 277
278 278 Uncommit leaving an empty changeset
279 279
280 280 $ cd $TESTTMP
281 281 $ hg init repo1
282 282 $ cd repo1
283 283 $ hg debugdrawdag <<'EOS'
284 284 > Q
285 285 > |
286 286 > P
287 287 > EOS
288 288 $ hg up Q -q
289 $ hg uncommit --empty
289 $ hg uncommit --keep
290 290 $ hg log -G -T '{desc} FILES: {files}'
291 291 @ Q FILES:
292 292 |
293 293 | x Q FILES: Q
294 294 |/
295 295 o P FILES: P
296 296
297 297 $ hg status
298 298 A Q
299 299
300 300 $ cd ..
301 301 $ rm -rf repo1
302 302
303 303 Testing uncommit while merge
304 304
305 305 $ hg init repo2
306 306 $ cd repo2
307 307
308 308 Create some history
309 309
310 310 $ touch a
311 311 $ hg add a
312 312 $ for i in 1 2 3; do echo $i > a; hg commit -m "a $i"; done
313 313 $ hg checkout 0
314 314 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
315 315 $ touch b
316 316 $ hg add b
317 317 $ for i in 1 2 3; do echo $i > b; hg commit -m "b $i"; done
318 318 created new head
319 319 $ hg log -G -T '{rev}:{node} {desc}' --hidden
320 320 @ 5:2cd56cdde163ded2fbb16ba2f918c96046ab0bf2 b 3
321 321 |
322 322 o 4:c3a0d5bb3b15834ffd2ef9ef603e93ec65cf2037 b 2
323 323 |
324 324 o 3:49bb009ca26078726b8870f1edb29fae8f7618f5 b 1
325 325 |
326 326 | o 2:990982b7384266e691f1bc08ca36177adcd1c8a9 a 3
327 327 | |
328 328 | o 1:24d38e3cf160c7b6f5ffe82179332229886a6d34 a 2
329 329 |/
330 330 o 0:ea4e33293d4d274a2ba73150733c2612231f398c a 1
331 331
332 332
333 333 Add and expect uncommit to fail on both merge working dir and merge changeset
334 334
335 335 $ hg merge 2
336 336 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
337 337 (branch merge, don't forget to commit)
338 338
339 339 $ hg uncommit
340 340 abort: cannot uncommit while merging
341 341 [255]
342 342
343 343 $ hg status
344 344 M a
345 345 $ hg commit -m 'merge a and b'
346 346
347 347 $ hg uncommit
348 348 abort: cannot uncommit merge changeset
349 349 [255]
350 350
351 351 $ hg status
352 352 $ hg log -G -T '{rev}:{node} {desc}' --hidden
353 353 @ 6:c03b9c37bc67bf504d4912061cfb527b47a63c6e merge a and b
354 354 |\
355 355 | o 5:2cd56cdde163ded2fbb16ba2f918c96046ab0bf2 b 3
356 356 | |
357 357 | o 4:c3a0d5bb3b15834ffd2ef9ef603e93ec65cf2037 b 2
358 358 | |
359 359 | o 3:49bb009ca26078726b8870f1edb29fae8f7618f5 b 1
360 360 | |
361 361 o | 2:990982b7384266e691f1bc08ca36177adcd1c8a9 a 3
362 362 | |
363 363 o | 1:24d38e3cf160c7b6f5ffe82179332229886a6d34 a 2
364 364 |/
365 365 o 0:ea4e33293d4d274a2ba73150733c2612231f398c a 1
366 366
General Comments 0
You need to be logged in to leave comments. Login now