##// END OF EJS Templates
unamend: allow unamending if allowunstable is set...
Martin von Zweigbergk -
r35451:f0110110 default
parent child Browse files
Show More
@@ -1,261 +1,257 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 cmdutil,
26 26 commands,
27 27 context,
28 28 copies,
29 29 error,
30 30 node,
31 31 obsutil,
32 32 pycompat,
33 33 registrar,
34 34 rewriteutil,
35 35 scmutil,
36 36 )
37 37
38 38 cmdtable = {}
39 39 command = registrar.command(cmdtable)
40 40
41 41 configtable = {}
42 42 configitem = registrar.configitem(configtable)
43 43
44 44 configitem('experimental', 'uncommitondirtywdir',
45 45 default=False,
46 46 )
47 47
48 48 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
49 49 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
50 50 # be specifying the version(s) of Mercurial they are tested with, or
51 51 # leave the attribute unspecified.
52 52 testedwith = 'ships-with-hg-core'
53 53
54 54 def _commitfiltered(repo, ctx, match, allowempty):
55 55 """Recommit ctx with changed files not in match. Return the new
56 56 node identifier, or None if nothing changed.
57 57 """
58 58 base = ctx.p1()
59 59 # ctx
60 60 initialfiles = set(ctx.files())
61 61 exclude = set(f for f in initialfiles if match(f))
62 62
63 63 # No files matched commit, so nothing excluded
64 64 if not exclude:
65 65 return None
66 66
67 67 files = (initialfiles - exclude)
68 68 # return the p1 so that we don't create an obsmarker later
69 69 if not files and not allowempty:
70 70 return ctx.parents()[0].node()
71 71
72 72 # Filter copies
73 73 copied = copies.pathcopies(base, ctx)
74 74 copied = dict((dst, src) for dst, src in copied.iteritems()
75 75 if dst in files)
76 76 def filectxfn(repo, memctx, path, contentctx=ctx, redirect=()):
77 77 if path not in contentctx:
78 78 return None
79 79 fctx = contentctx[path]
80 80 mctx = context.memfilectx(repo, memctx, fctx.path(), fctx.data(),
81 81 fctx.islink(),
82 82 fctx.isexec(),
83 83 copied=copied.get(path))
84 84 return mctx
85 85
86 86 new = context.memctx(repo,
87 87 parents=[base.node(), node.nullid],
88 88 text=ctx.description(),
89 89 files=files,
90 90 filectxfn=filectxfn,
91 91 user=ctx.user(),
92 92 date=ctx.date(),
93 93 extra=ctx.extra())
94 94 # phase handling
95 95 commitphase = ctx.phase()
96 96 overrides = {('phases', 'new-commit'): commitphase}
97 97 with repo.ui.configoverride(overrides, 'uncommit'):
98 98 newid = repo.commitctx(new)
99 99 return newid
100 100
101 101 def _fixdirstate(repo, oldctx, newctx, status):
102 102 """ fix the dirstate after switching the working directory from oldctx to
103 103 newctx which can be result of either unamend or uncommit.
104 104 """
105 105 ds = repo.dirstate
106 106 copies = dict(ds.copies())
107 107 s = status
108 108 for f in s.modified:
109 109 if ds[f] == 'r':
110 110 # modified + removed -> removed
111 111 continue
112 112 ds.normallookup(f)
113 113
114 114 for f in s.added:
115 115 if ds[f] == 'r':
116 116 # added + removed -> unknown
117 117 ds.drop(f)
118 118 elif ds[f] != 'a':
119 119 ds.add(f)
120 120
121 121 for f in s.removed:
122 122 if ds[f] == 'a':
123 123 # removed + added -> normal
124 124 ds.normallookup(f)
125 125 elif ds[f] != 'r':
126 126 ds.remove(f)
127 127
128 128 # Merge old parent and old working dir copies
129 129 oldcopies = {}
130 130 for f in (s.modified + s.added):
131 131 src = oldctx[f].renamed()
132 132 if src:
133 133 oldcopies[f] = src[0]
134 134 oldcopies.update(copies)
135 135 copies = dict((dst, oldcopies.get(src, src))
136 136 for dst, src in oldcopies.iteritems())
137 137 # Adjust the dirstate copies
138 138 for dst, src in copies.iteritems():
139 139 if (src not in newctx or dst in newctx or ds[dst] != 'a'):
140 140 src = None
141 141 ds.copy(src, dst)
142 142
143 143 @command('uncommit',
144 144 [('', 'keep', False, _('allow an empty commit after uncommiting')),
145 145 ] + commands.walkopts,
146 146 _('[OPTION]... [FILE]...'))
147 147 def uncommit(ui, repo, *pats, **opts):
148 148 """uncommit part or all of a local changeset
149 149
150 150 This command undoes the effect of a local commit, returning the affected
151 151 files to their uncommitted state. This means that files modified or
152 152 deleted in the changeset will be left unchanged, and so will remain
153 153 modified in the working directory.
154 154 """
155 155 opts = pycompat.byteskwargs(opts)
156 156
157 157 with repo.wlock(), repo.lock():
158 158
159 159 if not pats and not repo.ui.configbool('experimental',
160 160 'uncommitondirtywdir'):
161 161 cmdutil.bailifchanged(repo)
162 162 old = repo['.']
163 163 rewriteutil.precheck(repo, [old.rev()], 'uncommit')
164 164 if len(old.parents()) > 1:
165 165 raise error.Abort(_("cannot uncommit merge changeset"))
166 166
167 167 with repo.transaction('uncommit'):
168 168 match = scmutil.match(old, pats, opts)
169 169 newid = _commitfiltered(repo, old, match, opts.get('keep'))
170 170 if newid is None:
171 171 ui.status(_("nothing to uncommit\n"))
172 172 return 1
173 173
174 174 mapping = {}
175 175 if newid != old.p1().node():
176 176 # Move local changes on filtered changeset
177 177 mapping[old.node()] = (newid,)
178 178 else:
179 179 # Fully removed the old commit
180 180 mapping[old.node()] = ()
181 181
182 182 scmutil.cleanupnodes(repo, mapping, 'uncommit')
183 183
184 184 with repo.dirstate.parentchange():
185 185 repo.dirstate.setparents(newid, node.nullid)
186 186 s = repo.status(old.p1(), old, match=match)
187 187 _fixdirstate(repo, old, repo[newid], s)
188 188
189 189 def predecessormarkers(ctx):
190 190 """yields the obsolete markers marking the given changeset as a successor"""
191 191 for data in ctx.repo().obsstore.predecessors.get(ctx.node(), ()):
192 192 yield obsutil.marker(ctx.repo(), data)
193 193
194 194 @command('^unamend', [])
195 195 def unamend(ui, repo, **opts):
196 196 """
197 197 undo the most recent amend operation on a current changeset
198 198
199 199 This command will roll back to the previous version of a changeset,
200 200 leaving working directory in state in which it was before running
201 201 `hg amend` (e.g. files modified as part of an amend will be
202 202 marked as modified `hg status`)
203 203 """
204 204
205 205 unfi = repo.unfiltered()
206 206 with repo.wlock(), repo.lock(), repo.transaction('unamend'):
207 207
208 208 # identify the commit from which to unamend
209 209 curctx = repo['.']
210 210
211 if not curctx.mutable():
212 raise error.Abort(_('cannot unamend public changesets'))
211 rewriteutil.precheck(repo, [curctx.rev()], 'unamend')
213 212
214 213 # identify the commit to which to unamend
215 214 markers = list(predecessormarkers(curctx))
216 215 if len(markers) != 1:
217 216 e = _("changeset must have one predecessor, found %i predecessors")
218 217 raise error.Abort(e % len(markers))
219 218
220 219 prednode = markers[0].prednode()
221 220 predctx = unfi[prednode]
222 221
223 if curctx.children():
224 raise error.Abort(_("cannot unamend a changeset with children"))
225
226 222 # add an extra so that we get a new hash
227 223 # note: allowing unamend to undo an unamend is an intentional feature
228 224 extras = predctx.extra()
229 225 extras['unamend_source'] = curctx.hex()
230 226
231 227 def filectxfn(repo, ctx_, path):
232 228 try:
233 229 return predctx.filectx(path)
234 230 except KeyError:
235 231 return None
236 232
237 233 # Make a new commit same as predctx
238 234 newctx = context.memctx(repo,
239 235 parents=(predctx.p1(), predctx.p2()),
240 236 text=predctx.description(),
241 237 files=predctx.files(),
242 238 filectxfn=filectxfn,
243 239 user=predctx.user(),
244 240 date=predctx.date(),
245 241 extra=extras)
246 242 # phase handling
247 243 commitphase = curctx.phase()
248 244 overrides = {('phases', 'new-commit'): commitphase}
249 245 with repo.ui.configoverride(overrides, 'uncommit'):
250 246 newprednode = repo.commitctx(newctx)
251 247
252 248 newpredctx = repo[newprednode]
253 249 dirstate = repo.dirstate
254 250
255 251 with dirstate.parentchange():
256 252 dirstate.setparents(newprednode, node.nullid)
257 253 s = repo.status(predctx, curctx)
258 254 _fixdirstate(repo, curctx, newpredctx, s)
259 255
260 256 mapping = {curctx.node(): (newprednode,)}
261 257 scmutil.cleanupnodes(repo, mapping, 'unamend')
@@ -1,366 +1,369 b''
1 1 Test for command `hg unamend` which lives in uncommit extension
2 2 ===============================================================
3 3
4 4 $ cat >> $HGRCPATH << EOF
5 5 > [alias]
6 6 > glog = log -G -T '{rev}:{node|short} {desc}'
7 7 > [experimental]
8 8 > evolution = createmarkers, allowunstable
9 9 > [extensions]
10 10 > rebase =
11 11 > amend =
12 12 > uncommit =
13 13 > EOF
14 14
15 15 Repo Setup
16 16
17 17 $ hg init repo
18 18 $ cd repo
19 19 $ for ch in a b c d e f g h; do touch $ch; echo "foo" >> $ch; hg ci -Aqm "Added "$ch; done
20 20
21 21 $ hg glog
22 22 @ 7:ec2426147f0e Added h
23 23 |
24 24 o 6:87d6d6676308 Added g
25 25 |
26 26 o 5:825660c69f0c Added f
27 27 |
28 28 o 4:aa98ab95a928 Added e
29 29 |
30 30 o 3:62615734edd5 Added d
31 31 |
32 32 o 2:28ad74487de9 Added c
33 33 |
34 34 o 1:29becc82797a Added b
35 35 |
36 36 o 0:18d04c59bb5d Added a
37 37
38 38 Trying to unamend when there was no amend done
39 39
40 40 $ hg unamend
41 41 abort: changeset must have one predecessor, found 0 predecessors
42 42 [255]
43 43
44 44 Unamend on clean wdir and tip
45 45
46 46 $ echo "bar" >> h
47 47 $ hg amend
48 48
49 49 $ hg exp
50 50 # HG changeset patch
51 51 # User test
52 52 # Date 0 0
53 53 # Thu Jan 01 00:00:00 1970 +0000
54 54 # Node ID c9fa1a715c1b7661c0fafb362a9f30bd75878d7d
55 55 # Parent 87d6d66763085b629e6d7ed56778c79827273022
56 56 Added h
57 57
58 58 diff -r 87d6d6676308 -r c9fa1a715c1b h
59 59 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
60 60 +++ b/h Thu Jan 01 00:00:00 1970 +0000
61 61 @@ -0,0 +1,2 @@
62 62 +foo
63 63 +bar
64 64
65 65 $ hg glog --hidden
66 66 @ 8:c9fa1a715c1b Added h
67 67 |
68 68 | x 7:ec2426147f0e Added h
69 69 |/
70 70 o 6:87d6d6676308 Added g
71 71 |
72 72 o 5:825660c69f0c Added f
73 73 |
74 74 o 4:aa98ab95a928 Added e
75 75 |
76 76 o 3:62615734edd5 Added d
77 77 |
78 78 o 2:28ad74487de9 Added c
79 79 |
80 80 o 1:29becc82797a Added b
81 81 |
82 82 o 0:18d04c59bb5d Added a
83 83
84 84 $ hg unamend
85 85 $ hg glog --hidden
86 86 @ 9:46d02d47eec6 Added h
87 87 |
88 88 | x 8:c9fa1a715c1b Added h
89 89 |/
90 90 | x 7:ec2426147f0e Added h
91 91 |/
92 92 o 6:87d6d6676308 Added g
93 93 |
94 94 o 5:825660c69f0c Added f
95 95 |
96 96 o 4:aa98ab95a928 Added e
97 97 |
98 98 o 3:62615734edd5 Added d
99 99 |
100 100 o 2:28ad74487de9 Added c
101 101 |
102 102 o 1:29becc82797a Added b
103 103 |
104 104 o 0:18d04c59bb5d Added a
105 105
106 106 $ hg diff
107 107 diff -r 46d02d47eec6 h
108 108 --- a/h Thu Jan 01 00:00:00 1970 +0000
109 109 +++ b/h Thu Jan 01 00:00:00 1970 +0000
110 110 @@ -1,1 +1,2 @@
111 111 foo
112 112 +bar
113 113
114 114 $ hg exp
115 115 # HG changeset patch
116 116 # User test
117 117 # Date 0 0
118 118 # Thu Jan 01 00:00:00 1970 +0000
119 119 # Node ID 46d02d47eec6ca096b8dcab3f8f5579c40c3dd9a
120 120 # Parent 87d6d66763085b629e6d7ed56778c79827273022
121 121 Added h
122 122
123 123 diff -r 87d6d6676308 -r 46d02d47eec6 h
124 124 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
125 125 +++ b/h Thu Jan 01 00:00:00 1970 +0000
126 126 @@ -0,0 +1,1 @@
127 127 +foo
128 128
129 129 $ hg status
130 130 M h
131 131
132 132 $ hg log -r . -T '{extras % "{extra}\n"}' --config alias.log=log
133 133 branch=default
134 134 unamend_source=c9fa1a715c1b7661c0fafb362a9f30bd75878d7d
135 135
136 136 Using unamend to undo an unamed (intentional)
137 137
138 138 $ hg unamend
139 139 $ hg exp
140 140 # HG changeset patch
141 141 # User test
142 142 # Date 0 0
143 143 # Thu Jan 01 00:00:00 1970 +0000
144 144 # Node ID 850ddfc1bc662997ec6094ada958f01f0cc8070a
145 145 # Parent 87d6d66763085b629e6d7ed56778c79827273022
146 146 Added h
147 147
148 148 diff -r 87d6d6676308 -r 850ddfc1bc66 h
149 149 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
150 150 +++ b/h Thu Jan 01 00:00:00 1970 +0000
151 151 @@ -0,0 +1,2 @@
152 152 +foo
153 153 +bar
154 154 $ hg diff
155 155
156 156 Unamend on a dirty working directory
157 157
158 158 $ echo "bar" >> a
159 159 $ hg amend
160 160 $ echo "foobar" >> a
161 161 $ echo "bar" >> b
162 162 $ hg status
163 163 M a
164 164 M b
165 165
166 166 $ hg unamend
167 167
168 168 $ hg status
169 169 M a
170 170 M b
171 171
172 172 $ hg diff
173 173 diff -r ec338db45d51 a
174 174 --- a/a Thu Jan 01 00:00:00 1970 +0000
175 175 +++ b/a Thu Jan 01 00:00:00 1970 +0000
176 176 @@ -1,1 +1,3 @@
177 177 foo
178 178 +bar
179 179 +foobar
180 180 diff -r ec338db45d51 b
181 181 --- a/b Thu Jan 01 00:00:00 1970 +0000
182 182 +++ b/b Thu Jan 01 00:00:00 1970 +0000
183 183 @@ -1,1 +1,2 @@
184 184 foo
185 185 +bar
186 186
187 187 Unamending an added file
188 188
189 189 $ hg ci -m "Added things to a and b"
190 190 $ echo foo > bar
191 191 $ hg add bar
192 192 $ hg amend
193 193
194 194 $ hg unamend
195 195 $ hg status
196 196 A bar
197 197
198 198 $ hg revert --all
199 199 forgetting bar
200 200
201 201 Unamending a removed file
202 202
203 203 $ hg remove a
204 204 $ hg amend
205 205
206 206 $ hg unamend
207 207 $ hg status
208 208 R a
209 209 ? bar
210 210
211 211 $ hg revert --all
212 212 undeleting a
213 213
214 214 Unamending an added file with dirty wdir status
215 215
216 216 $ hg add bar
217 217 $ hg amend
218 218 $ echo bar >> bar
219 219 $ hg status
220 220 M bar
221 221
222 222 $ hg unamend
223 223 $ hg status
224 224 A bar
225 225 $ hg diff
226 226 diff -r 7f79409af972 bar
227 227 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
228 228 +++ b/bar Thu Jan 01 00:00:00 1970 +0000
229 229 @@ -0,0 +1,2 @@
230 230 +foo
231 231 +bar
232 232
233 233 $ hg revert --all
234 234 forgetting bar
235 235
236 236 Unamending in middle of a stack
237 237
238 238 $ hg glog
239 239 @ 19:7f79409af972 Added things to a and b
240 240 |
241 241 o 12:ec338db45d51 Added h
242 242 |
243 243 o 6:87d6d6676308 Added g
244 244 |
245 245 o 5:825660c69f0c Added f
246 246 |
247 247 o 4:aa98ab95a928 Added e
248 248 |
249 249 o 3:62615734edd5 Added d
250 250 |
251 251 o 2:28ad74487de9 Added c
252 252 |
253 253 o 1:29becc82797a Added b
254 254 |
255 255 o 0:18d04c59bb5d Added a
256 256
257 257 $ hg up 5
258 258 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
259 259 $ echo bar >> f
260 260 $ hg amend
261 261 $ hg rebase -s 6 -d . -q
262 262
263 263 $ hg glog
264 264 o 23:03ddd6fc5af1 Added things to a and b
265 265 |
266 266 o 22:3e7b64ee157b Added h
267 267 |
268 268 o 21:49635b68477e Added g
269 269 |
270 270 @ 20:93f0e8ffab32 Added f
271 271 |
272 272 o 4:aa98ab95a928 Added e
273 273 |
274 274 o 3:62615734edd5 Added d
275 275 |
276 276 o 2:28ad74487de9 Added c
277 277 |
278 278 o 1:29becc82797a Added b
279 279 |
280 280 o 0:18d04c59bb5d Added a
281 281
282 282
283 $ hg --config experimental.evolution=createmarkers unamend
284 abort: cannot unamend changeset with children
285 [255]
286
283 287 $ hg unamend
284 abort: cannot unamend a changeset with children
285 [255]
286 288
287 289 Trying to unamend a public changeset
288 290
289 $ hg up
290 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
291 $ hg up -C 23
292 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
291 293 $ hg phase -r . -p
292 294 $ hg unamend
293 295 abort: cannot unamend public changesets
296 (see 'hg help phases' for details)
294 297 [255]
295 298
296 299 Testing whether unamend retains copies or not
297 300
298 301 $ hg status
299 302 ? bar
300 303
301 304 $ hg mv a foo
302 305
303 306 $ hg ci -m "Moved a to foo"
304 307 $ hg exp --git
305 308 # HG changeset patch
306 309 # User test
307 310 # Date 0 0
308 311 # Thu Jan 01 00:00:00 1970 +0000
309 312 # Node ID cfef290346fbee5126313d7e1aab51d877679b09
310 313 # Parent 03ddd6fc5af19e028c44a2fd6d790dd22712f231
311 314 Moved a to foo
312 315
313 316 diff --git a/a b/foo
314 317 rename from a
315 318 rename to foo
316 319
317 320 $ hg mv b foobar
318 321 $ hg diff --git
319 322 diff --git a/b b/foobar
320 323 rename from b
321 324 rename to foobar
322 325 $ hg amend
323 326
324 327 $ hg exp --git
325 328 # HG changeset patch
326 329 # User test
327 330 # Date 0 0
328 331 # Thu Jan 01 00:00:00 1970 +0000
329 332 # Node ID eca050985275bb271ce3092b54e56ea5c85d29a3
330 333 # Parent 03ddd6fc5af19e028c44a2fd6d790dd22712f231
331 334 Moved a to foo
332 335
333 336 diff --git a/a b/foo
334 337 rename from a
335 338 rename to foo
336 339 diff --git a/b b/foobar
337 340 rename from b
338 341 rename to foobar
339 342
340 343 $ hg mv c wat
341 344 $ hg unamend
342 345
343 346 Retained copies in new prdecessor commit
344 347
345 348 $ hg exp --git
346 349 # HG changeset patch
347 350 # User test
348 351 # Date 0 0
349 352 # Thu Jan 01 00:00:00 1970 +0000
350 353 # Node ID 552e3af4f01f620f88ca27be1f898316235b736a
351 354 # Parent 03ddd6fc5af19e028c44a2fd6d790dd22712f231
352 355 Moved a to foo
353 356
354 357 diff --git a/a b/foo
355 358 rename from a
356 359 rename to foo
357 360
358 361 Retained copies in working directoy
359 362
360 363 $ hg diff --git
361 364 diff --git a/b b/foobar
362 365 rename from b
363 366 rename to foobar
364 367 diff --git a/c b/wat
365 368 rename from c
366 369 rename to wat
General Comments 0
You need to be logged in to leave comments. Login now