##// END OF EJS Templates
unamend: drop unused vars, query after taking lock, use ctx.hex() for extras...
Pulkit Goyal -
r35201:9e339c97 default
parent child Browse files
Show More
@@ -1,277 +1,270 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 obsolete,
32 32 obsutil,
33 33 pycompat,
34 34 registrar,
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, 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 wctx = repo[None]
159 159
160 160 if not pats and not repo.ui.configbool('experimental',
161 161 'uncommitondirtywdir'):
162 162 cmdutil.bailifchanged(repo)
163 163 if wctx.parents()[0].node() == node.nullid:
164 164 raise error.Abort(_("cannot uncommit null changeset"))
165 165 if len(wctx.parents()) > 1:
166 166 raise error.Abort(_("cannot uncommit while merging"))
167 167 old = repo['.']
168 168 if not old.mutable():
169 169 raise error.Abort(_('cannot uncommit public changesets'))
170 170 if len(old.parents()) > 1:
171 171 raise error.Abort(_("cannot uncommit merge changeset"))
172 172 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
173 173 if not allowunstable and old.children():
174 174 raise error.Abort(_('cannot uncommit changeset with children'))
175 175
176 176 with repo.transaction('uncommit'):
177 177 match = scmutil.match(old, pats, opts)
178 178 newid = _commitfiltered(repo, old, match, opts.get('keep'))
179 179 if newid is None:
180 180 ui.status(_("nothing to uncommit\n"))
181 181 return 1
182 182
183 183 mapping = {}
184 184 if newid != old.p1().node():
185 185 # Move local changes on filtered changeset
186 186 mapping[old.node()] = (newid,)
187 187 else:
188 188 # Fully removed the old commit
189 189 mapping[old.node()] = ()
190 190
191 191 scmutil.cleanupnodes(repo, mapping, 'uncommit')
192 192
193 193 with repo.dirstate.parentchange():
194 194 repo.dirstate.setparents(newid, node.nullid)
195 195 s = repo.status(old.p1(), old, match=match)
196 196 _fixdirstate(repo, old, repo[newid], s)
197 197
198 198 def predecessormarkers(ctx):
199 199 """yields the obsolete markers marking the given changeset as a successor"""
200 200 for data in ctx.repo().obsstore.predecessors.get(ctx.node(), ()):
201 201 yield obsutil.marker(ctx.repo(), data)
202 202
203 203 @command('^unamend', [])
204 204 def unamend(ui, repo, **opts):
205 205 """
206 206 undo the most recent amend operation on a current changeset
207 207
208 208 This command will roll back to the previous version of a changeset,
209 209 leaving working directory in state in which it was before running
210 210 `hg amend` (e.g. files modified as part of an amend will be
211 211 marked as modified `hg status`)
212 212 """
213 213
214 214 unfi = repo.unfiltered()
215 with repo.wlock(), repo.lock(), repo.transaction('unamend'):
215 216
216 # identify the commit from which to unamend
217 curctx = repo['.']
217 # identify the commit from which to unamend
218 curctx = repo['.']
218 219
219 with repo.wlock(), repo.lock(), repo.transaction('unamend'):
220 220 if not curctx.mutable():
221 221 raise error.Abort(_('cannot unamend public changesets'))
222 222
223 223 # identify the commit to which to unamend
224 224 markers = list(predecessormarkers(curctx))
225 225 if len(markers) != 1:
226 226 e = _("changeset must have one predecessor, found %i predecessors")
227 227 raise error.Abort(e % len(markers))
228 228
229 229 prednode = markers[0].prednode()
230 230 predctx = unfi[prednode]
231 231
232 232 if curctx.children():
233 233 raise error.Abort(_("cannot unamend a changeset with children"))
234 234
235 235 # add an extra so that we get a new hash
236 236 # note: allowing unamend to undo an unamend is an intentional feature
237 237 extras = predctx.extra()
238 extras['unamend_source'] = curctx.node()
238 extras['unamend_source'] = curctx.hex()
239 239
240 240 def filectxfn(repo, ctx_, path):
241 241 try:
242 242 return predctx.filectx(path)
243 243 except KeyError:
244 244 return None
245 245
246 246 # Make a new commit same as predctx
247 247 newctx = context.memctx(repo,
248 248 parents=(predctx.p1(), predctx.p2()),
249 249 text=predctx.description(),
250 250 files=predctx.files(),
251 251 filectxfn=filectxfn,
252 252 user=predctx.user(),
253 253 date=predctx.date(),
254 254 extra=extras)
255 255 # phase handling
256 256 commitphase = curctx.phase()
257 257 overrides = {('phases', 'new-commit'): commitphase}
258 258 with repo.ui.configoverride(overrides, 'uncommit'):
259 259 newprednode = repo.commitctx(newctx)
260 260
261 261 newpredctx = repo[newprednode]
262
263 changedfiles = []
264 wctx = repo[None]
265 wm = wctx.manifest()
266 cm = newpredctx.manifest()
267 262 dirstate = repo.dirstate
268 diff = cm.diff(wm)
269 changedfiles.extend(diff.iterkeys())
270 263
271 264 with dirstate.parentchange():
272 265 dirstate.setparents(newprednode, node.nullid)
273 266 s = repo.status(predctx, curctx)
274 267 _fixdirstate(repo, curctx, newpredctx, s)
275 268
276 269 mapping = {curctx.node(): (newprednode,)}
277 270 scmutil.cleanupnodes(repo, mapping, 'unamend')
@@ -1,366 +1,366 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 @ 9:8da14a1fd653 Added h
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 diff -r 8da14a1fd653 h
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 # Node ID 8da14a1fd653c3f07fdad5760511c9e12652a306
119 # Node ID 46d02d47eec6ca096b8dcab3f8f5579c40c3dd9a
120 120 # Parent 87d6d66763085b629e6d7ed56778c79827273022
121 121 Added h
122 122
123 diff -r 87d6d6676308 -r 8da14a1fd653 h
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 unamend_source=\xc9\xfa\x1aq\\\x1bva\xc0\xfa\xfb6*\x9f0\xbdu\x87\x8d}
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 # Node ID 1c09887216571a9755fc9d2e7f0e41c2b49e341b
144 # Node ID 850ddfc1bc662997ec6094ada958f01f0cc8070a
145 145 # Parent 87d6d66763085b629e6d7ed56778c79827273022
146 146 Added h
147 147
148 diff -r 87d6d6676308 -r 1c0988721657 h
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 diff -r 956548224719 a
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 diff -r 956548224719 b
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 diff -r ff917aa01c86 bar
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 @ 19:ff917aa01c86 Added things to a and b
239 @ 19:7f79409af972 Added things to a and b
240 240 |
241 o 12:956548224719 Added h
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 o 23:2b41b42e192a Added things to a and b
264 o 23:03ddd6fc5af1 Added things to a and b
265 265 |
266 o 22:1860df151f01 Added h
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 283 $ hg unamend
284 284 abort: cannot unamend a changeset with children
285 285 [255]
286 286
287 287 Trying to unamend a public changeset
288 288
289 289 $ hg up
290 290 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
291 291 $ hg phase -r . -p
292 292 $ hg unamend
293 293 abort: cannot unamend public changesets
294 294 [255]
295 295
296 296 Testing whether unamend retains copies or not
297 297
298 298 $ hg status
299 299 ? bar
300 300
301 301 $ hg mv a foo
302 302
303 303 $ hg ci -m "Moved a to foo"
304 304 $ hg exp --git
305 305 # HG changeset patch
306 306 # User test
307 307 # Date 0 0
308 308 # Thu Jan 01 00:00:00 1970 +0000
309 # Node ID b087c66cada118d6c5487d3d7cb29cac239bd98a
310 # Parent 2b41b42e192adc425b106643c3fc89170d6b8add
309 # Node ID cfef290346fbee5126313d7e1aab51d877679b09
310 # Parent 03ddd6fc5af19e028c44a2fd6d790dd22712f231
311 311 Moved a to foo
312 312
313 313 diff --git a/a b/foo
314 314 rename from a
315 315 rename to foo
316 316
317 317 $ hg mv b foobar
318 318 $ hg diff --git
319 319 diff --git a/b b/foobar
320 320 rename from b
321 321 rename to foobar
322 322 $ hg amend
323 323
324 324 $ hg exp --git
325 325 # HG changeset patch
326 326 # User test
327 327 # Date 0 0
328 328 # Thu Jan 01 00:00:00 1970 +0000
329 # Node ID 9fa06fb09a83a86ec7368d15004dbc52ac1a5d2e
330 # Parent 2b41b42e192adc425b106643c3fc89170d6b8add
329 # Node ID eca050985275bb271ce3092b54e56ea5c85d29a3
330 # Parent 03ddd6fc5af19e028c44a2fd6d790dd22712f231
331 331 Moved a to foo
332 332
333 333 diff --git a/a b/foo
334 334 rename from a
335 335 rename to foo
336 336 diff --git a/b b/foobar
337 337 rename from b
338 338 rename to foobar
339 339
340 340 $ hg mv c wat
341 341 $ hg unamend
342 342
343 343 Retained copies in new prdecessor commit
344 344
345 345 $ hg exp --git
346 346 # HG changeset patch
347 347 # User test
348 348 # Date 0 0
349 349 # Thu Jan 01 00:00:00 1970 +0000
350 # Node ID 4cf9987c9b941f615930e7c46307b4dbf35697cf
351 # Parent 2b41b42e192adc425b106643c3fc89170d6b8add
350 # Node ID 552e3af4f01f620f88ca27be1f898316235b736a
351 # Parent 03ddd6fc5af19e028c44a2fd6d790dd22712f231
352 352 Moved a to foo
353 353
354 354 diff --git a/a b/foo
355 355 rename from a
356 356 rename to foo
357 357
358 358 Retained copies in working directoy
359 359
360 360 $ hg diff --git
361 361 diff --git a/b b/foobar
362 362 rename from b
363 363 rename to foobar
364 364 diff --git a/c b/wat
365 365 rename from c
366 366 rename to wat
General Comments 0
You need to be logged in to leave comments. Login now