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