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