##// END OF EJS Templates
unamend: fix unamending of renamed rename...
Martin von Zweigbergk -
r41372:19c590ce default
parent child Browse files
Show More
@@ -1,252 +1,246
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 as copiesmod,
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, keepcommit):
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 keepcommit:
70 70 return ctx.parents()[0].node()
71 71
72 72 # Filter copies
73 73 copied = copiesmod.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 return repo.commitctx(new)
95 95
96 def _fixdirstate(repo, oldctx, newctx, status):
96 def _fixdirstate(repo, oldctx, newctx, match=None):
97 97 """ fix the dirstate after switching the working directory from oldctx to
98 98 newctx which can be result of either unamend or uncommit.
99 99 """
100 100 ds = repo.dirstate
101 101 copies = dict(ds.copies())
102 s = status
102 s = newctx.status(oldctx, match=match)
103 103 for f in s.modified:
104 104 if ds[f] == 'r':
105 105 # modified + removed -> removed
106 106 continue
107 107 ds.normallookup(f)
108 108
109 109 for f in s.added:
110 110 if ds[f] == 'r':
111 111 # added + removed -> unknown
112 112 ds.drop(f)
113 113 elif ds[f] != 'a':
114 114 ds.add(f)
115 115
116 116 for f in s.removed:
117 117 if ds[f] == 'a':
118 118 # removed + added -> normal
119 119 ds.normallookup(f)
120 120 elif ds[f] != 'r':
121 121 ds.remove(f)
122 122
123 123 # Merge old parent and old working dir copies
124 oldcopies = {}
125 for f in (s.modified + s.added):
126 src = oldctx[f].renamed()
127 if src:
128 oldcopies[f] = src[0]
124 oldcopies = copiesmod.pathcopies(newctx, oldctx, match)
129 125 oldcopies.update(copies)
130 126 copies = dict((dst, oldcopies.get(src, src))
131 127 for dst, src in oldcopies.iteritems())
132 128 # Adjust the dirstate copies
133 129 for dst, src in copies.iteritems():
134 130 if (src not in newctx or dst in newctx or ds[dst] != 'a'):
135 131 src = None
136 132 ds.copy(src, dst)
137 133
138 134 @command('uncommit',
139 135 [('', 'keep', False, _('allow an empty commit after uncommiting')),
140 136 ] + commands.walkopts,
141 137 _('[OPTION]... [FILE]...'),
142 138 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
143 139 def uncommit(ui, repo, *pats, **opts):
144 140 """uncommit part or all of a local changeset
145 141
146 142 This command undoes the effect of a local commit, returning the affected
147 143 files to their uncommitted state. This means that files modified or
148 144 deleted in the changeset will be left unchanged, and so will remain
149 145 modified in the working directory.
150 146
151 147 If no files are specified, the commit will be pruned, unless --keep is
152 148 given.
153 149 """
154 150 opts = pycompat.byteskwargs(opts)
155 151
156 152 with repo.wlock(), repo.lock():
157 153
158 154 if not pats and not repo.ui.configbool('experimental',
159 155 'uncommitondirtywdir'):
160 156 cmdutil.bailifchanged(repo)
161 157 old = repo['.']
162 158 rewriteutil.precheck(repo, [old.rev()], 'uncommit')
163 159 if len(old.parents()) > 1:
164 160 raise error.Abort(_("cannot uncommit merge changeset"))
165 161
166 162 with repo.transaction('uncommit'):
167 163 match = scmutil.match(old, pats, opts)
168 164 keepcommit = opts.get('keep') or pats
169 165 newid = _commitfiltered(repo, old, match, keepcommit)
170 166 if newid is None:
171 167 ui.status(_("nothing to uncommit\n"))
172 168 return 1
173 169
174 170 mapping = {}
175 171 if newid != old.p1().node():
176 172 # Move local changes on filtered changeset
177 173 mapping[old.node()] = (newid,)
178 174 else:
179 175 # Fully removed the old commit
180 176 mapping[old.node()] = ()
181 177
182 178 with repo.dirstate.parentchange():
183 179 repo.dirstate.setparents(newid, node.nullid)
184 s = old.p1().status(old, match=match)
185 _fixdirstate(repo, old, repo[newid], s)
180 _fixdirstate(repo, old, repo[newid], match)
186 181
187 182 scmutil.cleanupnodes(repo, mapping, 'uncommit', fixphase=True)
188 183
189 184 def predecessormarkers(ctx):
190 185 """yields the obsolete markers marking the given changeset as a successor"""
191 186 for data in ctx.repo().obsstore.predecessors.get(ctx.node(), ()):
192 187 yield obsutil.marker(ctx.repo(), data)
193 188
194 189 @command('unamend', [], helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
195 190 helpbasic=True)
196 191 def unamend(ui, repo, **opts):
197 192 """undo the most recent amend operation on a current changeset
198 193
199 194 This command will roll back to the previous version of a changeset,
200 195 leaving working directory in state in which it was before running
201 196 `hg amend` (e.g. files modified as part of an amend will be
202 197 marked as modified `hg status`)
203 198 """
204 199
205 200 unfi = repo.unfiltered()
206 201 with repo.wlock(), repo.lock(), repo.transaction('unamend'):
207 202
208 203 # identify the commit from which to unamend
209 204 curctx = repo['.']
210 205
211 206 rewriteutil.precheck(repo, [curctx.rev()], 'unamend')
212 207
213 208 # identify the commit to which to unamend
214 209 markers = list(predecessormarkers(curctx))
215 210 if len(markers) != 1:
216 211 e = _("changeset must have one predecessor, found %i predecessors")
217 212 raise error.Abort(e % len(markers))
218 213
219 214 prednode = markers[0].prednode()
220 215 predctx = unfi[prednode]
221 216
222 217 # add an extra so that we get a new hash
223 218 # note: allowing unamend to undo an unamend is an intentional feature
224 219 extras = predctx.extra()
225 220 extras['unamend_source'] = curctx.hex()
226 221
227 222 def filectxfn(repo, ctx_, path):
228 223 try:
229 224 return predctx.filectx(path)
230 225 except KeyError:
231 226 return None
232 227
233 228 # Make a new commit same as predctx
234 229 newctx = context.memctx(repo,
235 230 parents=(predctx.p1(), predctx.p2()),
236 231 text=predctx.description(),
237 232 files=predctx.files(),
238 233 filectxfn=filectxfn,
239 234 user=predctx.user(),
240 235 date=predctx.date(),
241 236 extra=extras)
242 237 newprednode = repo.commitctx(newctx)
243 238 newpredctx = repo[newprednode]
244 239 dirstate = repo.dirstate
245 240
246 241 with dirstate.parentchange():
247 242 dirstate.setparents(newprednode, node.nullid)
248 s = repo.status(predctx, curctx)
249 _fixdirstate(repo, curctx, newpredctx, s)
243 _fixdirstate(repo, curctx, newpredctx)
250 244
251 245 mapping = {curctx.node(): (newprednode,)}
252 246 scmutil.cleanupnodes(repo, mapping, 'unamend', fixphase=True)
@@ -1,411 +1,411
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 $ rm bar
236 236
237 237 Unamending in middle of a stack
238 238
239 239 $ hg glog
240 240 @ 19:7f79409af972 Added things to a and b
241 241 |
242 242 o 12:ec338db45d51 Added h
243 243 |
244 244 o 6:87d6d6676308 Added g
245 245 |
246 246 o 5:825660c69f0c Added f
247 247 |
248 248 o 4:aa98ab95a928 Added e
249 249 |
250 250 o 3:62615734edd5 Added d
251 251 |
252 252 o 2:28ad74487de9 Added c
253 253 |
254 254 o 1:29becc82797a Added b
255 255 |
256 256 o 0:18d04c59bb5d Added a
257 257
258 258 $ hg up 5
259 259 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
260 260 $ echo bar >> f
261 261 $ hg amend
262 262 3 new orphan changesets
263 263 $ hg rebase -s 6 -d . -q
264 264
265 265 $ hg glog
266 266 o 23:03ddd6fc5af1 Added things to a and b
267 267 |
268 268 o 22:3e7b64ee157b Added h
269 269 |
270 270 o 21:49635b68477e Added g
271 271 |
272 272 @ 20:93f0e8ffab32 Added f
273 273 |
274 274 o 4:aa98ab95a928 Added e
275 275 |
276 276 o 3:62615734edd5 Added d
277 277 |
278 278 o 2:28ad74487de9 Added c
279 279 |
280 280 o 1:29becc82797a Added b
281 281 |
282 282 o 0:18d04c59bb5d Added a
283 283
284 284
285 285 $ hg --config experimental.evolution=createmarkers unamend
286 286 abort: cannot unamend changeset with children
287 287 [255]
288 288
289 289 $ hg unamend
290 290 3 new orphan changesets
291 291
292 292 Trying to unamend a public changeset
293 293
294 294 $ hg up -C 23
295 295 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
296 296 $ hg phase -r . -p
297 297 1 new phase-divergent changesets
298 298 $ hg unamend
299 299 abort: cannot unamend public changesets
300 300 (see 'hg help phases' for details)
301 301 [255]
302 302
303 303 Testing whether unamend retains copies or not
304 304
305 305 $ hg status
306 306
307 307 $ hg mv a foo
308 308
309 309 $ hg ci -m "Moved a to foo"
310 310 $ hg exp --git
311 311 # HG changeset patch
312 312 # User test
313 313 # Date 0 0
314 314 # Thu Jan 01 00:00:00 1970 +0000
315 315 # Node ID cfef290346fbee5126313d7e1aab51d877679b09
316 316 # Parent 03ddd6fc5af19e028c44a2fd6d790dd22712f231
317 317 Moved a to foo
318 318
319 319 diff --git a/a b/foo
320 320 rename from a
321 321 rename to foo
322 322
323 323 $ hg mv b foobar
324 324 $ hg diff --git
325 325 diff --git a/b b/foobar
326 326 rename from b
327 327 rename to foobar
328 328 $ hg amend
329 329
330 330 $ hg exp --git
331 331 # HG changeset patch
332 332 # User test
333 333 # Date 0 0
334 334 # Thu Jan 01 00:00:00 1970 +0000
335 335 # Node ID eca050985275bb271ce3092b54e56ea5c85d29a3
336 336 # Parent 03ddd6fc5af19e028c44a2fd6d790dd22712f231
337 337 Moved a to foo
338 338
339 339 diff --git a/a b/foo
340 340 rename from a
341 341 rename to foo
342 342 diff --git a/b b/foobar
343 343 rename from b
344 344 rename to foobar
345 345
346 346 $ hg mv c wat
347 347 $ hg unamend
348 348
349 349 Retained copies in new prdecessor commit
350 350
351 351 $ hg exp --git
352 352 # HG changeset patch
353 353 # User test
354 354 # Date 0 0
355 355 # Thu Jan 01 00:00:00 1970 +0000
356 356 # Node ID 552e3af4f01f620f88ca27be1f898316235b736a
357 357 # Parent 03ddd6fc5af19e028c44a2fd6d790dd22712f231
358 358 Moved a to foo
359 359
360 360 diff --git a/a b/foo
361 361 rename from a
362 362 rename to foo
363 363
364 364 Retained copies in working directoy
365 365
366 366 $ hg diff --git
367 367 diff --git a/b b/foobar
368 368 rename from b
369 369 rename to foobar
370 370 diff --git a/c b/wat
371 371 rename from c
372 372 rename to wat
373 373 $ hg revert -qa
374 374 $ rm foobar wat
375 375
376 376 Rename a->b, then amend b->c. After unamend, should look like b->c.
377 377
378 378 $ hg co -q 0
379 379 $ hg mv a b
380 380 $ hg ci -qm 'move to a b'
381 381 $ hg mv b c
382 382 $ hg amend
383 383 $ hg unamend
384 384 $ hg st --copies --change .
385 385 A b
386 386 a
387 387 R a
388 BROKEN: should indicate that b was renamed to c
389 388 $ hg st --copies
390 389 A c
390 b
391 391 R b
392 392 $ hg revert -qa
393 393 $ rm c
394 394
395 395 Rename a->b, then amend b->c, and working copy change c->d. After unamend, should look like b->d
396 396
397 397 $ hg co -q 0
398 398 $ hg mv a b
399 399 $ hg ci -qm 'move to a b'
400 400 $ hg mv b c
401 401 $ hg amend
402 402 $ hg mv c d
403 403 $ hg unamend
404 404 $ hg st --copies --change .
405 405 A b
406 406 a
407 407 R a
408 BROKEN: should indicate that b was renamed to d
409 408 $ hg st --copies
410 409 A d
410 b
411 411 R b
General Comments 0
You need to be logged in to leave comments. Login now