##// END OF EJS Templates
uncommit: inform user if the commit is empty after uncommit...
Martin von Zweigbergk -
r41895:c10652d1 default draft
parent child Browse files
Show More
@@ -1,450 +1,453 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 as copiesmod,
28 copies as copiesmod,
29 error,
29 error,
30 node,
30 node,
31 obsolete,
31 obsolete,
32 obsutil,
32 obsutil,
33 patch,
33 patch,
34 pycompat,
34 pycompat,
35 registrar,
35 registrar,
36 rewriteutil,
36 rewriteutil,
37 scmutil,
37 scmutil,
38 util,
38 util,
39 )
39 )
40
40
41 cmdtable = {}
41 cmdtable = {}
42 command = registrar.command(cmdtable)
42 command = registrar.command(cmdtable)
43
43
44 configtable = {}
44 configtable = {}
45 configitem = registrar.configitem(configtable)
45 configitem = registrar.configitem(configtable)
46
46
47 configitem('experimental', 'uncommitondirtywdir',
47 configitem('experimental', 'uncommitondirtywdir',
48 default=False,
48 default=False,
49 )
49 )
50
50
51 stringio = util.stringio
51 stringio = util.stringio
52
52
53 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
53 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
54 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
54 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
55 # be specifying the version(s) of Mercurial they are tested with, or
55 # be specifying the version(s) of Mercurial they are tested with, or
56 # leave the attribute unspecified.
56 # leave the attribute unspecified.
57 testedwith = 'ships-with-hg-core'
57 testedwith = 'ships-with-hg-core'
58
58
59 def _commitfiltered(repo, ctx, match, keepcommit):
59 def _commitfiltered(repo, ctx, match, keepcommit):
60 """Recommit ctx with changed files not in match. Return the new
60 """Recommit ctx with changed files not in match. Return the new
61 node identifier, or None if nothing changed.
61 node identifier, or None if nothing changed.
62 """
62 """
63 base = ctx.p1()
63 base = ctx.p1()
64 # ctx
64 # ctx
65 initialfiles = set(ctx.files())
65 initialfiles = set(ctx.files())
66 exclude = set(f for f in initialfiles if match(f))
66 exclude = set(f for f in initialfiles if match(f))
67
67
68 # No files matched commit, so nothing excluded
68 # No files matched commit, so nothing excluded
69 if not exclude:
69 if not exclude:
70 return None
70 return None
71
71
72 files = (initialfiles - exclude)
73 # return the p1 so that we don't create an obsmarker later
72 # return the p1 so that we don't create an obsmarker later
74 if not keepcommit:
73 if not keepcommit:
75 return ctx.p1().node()
74 return ctx.p1().node()
76
75
76 files = (initialfiles - exclude)
77 # Filter copies
77 # Filter copies
78 copied = copiesmod.pathcopies(base, ctx)
78 copied = copiesmod.pathcopies(base, ctx)
79 copied = dict((dst, src) for dst, src in copied.iteritems()
79 copied = dict((dst, src) for dst, src in copied.iteritems()
80 if dst in files)
80 if dst in files)
81 def filectxfn(repo, memctx, path, contentctx=ctx, redirect=()):
81 def filectxfn(repo, memctx, path, contentctx=ctx, redirect=()):
82 if path not in contentctx:
82 if path not in contentctx:
83 return None
83 return None
84 fctx = contentctx[path]
84 fctx = contentctx[path]
85 mctx = context.memfilectx(repo, memctx, fctx.path(), fctx.data(),
85 mctx = context.memfilectx(repo, memctx, fctx.path(), fctx.data(),
86 fctx.islink(),
86 fctx.islink(),
87 fctx.isexec(),
87 fctx.isexec(),
88 copied=copied.get(path))
88 copied=copied.get(path))
89 return mctx
89 return mctx
90
90
91 if not files:
92 repo.ui.status(_("note: keeping empty commit\n"))
93
91 new = context.memctx(repo,
94 new = context.memctx(repo,
92 parents=[base.node(), node.nullid],
95 parents=[base.node(), node.nullid],
93 text=ctx.description(),
96 text=ctx.description(),
94 files=files,
97 files=files,
95 filectxfn=filectxfn,
98 filectxfn=filectxfn,
96 user=ctx.user(),
99 user=ctx.user(),
97 date=ctx.date(),
100 date=ctx.date(),
98 extra=ctx.extra())
101 extra=ctx.extra())
99 return repo.commitctx(new)
102 return repo.commitctx(new)
100
103
101 def _fixdirstate(repo, oldctx, newctx, match=None):
104 def _fixdirstate(repo, oldctx, newctx, match=None):
102 """ fix the dirstate after switching the working directory from oldctx to
105 """ fix the dirstate after switching the working directory from oldctx to
103 newctx which can be result of either unamend or uncommit.
106 newctx which can be result of either unamend or uncommit.
104 """
107 """
105 ds = repo.dirstate
108 ds = repo.dirstate
106 ds.setparents(newctx.node(), node.nullid)
109 ds.setparents(newctx.node(), node.nullid)
107 copies = dict(ds.copies())
110 copies = dict(ds.copies())
108 s = newctx.status(oldctx, match=match)
111 s = newctx.status(oldctx, match=match)
109 for f in s.modified:
112 for f in s.modified:
110 if ds[f] == 'r':
113 if ds[f] == 'r':
111 # modified + removed -> removed
114 # modified + removed -> removed
112 continue
115 continue
113 ds.normallookup(f)
116 ds.normallookup(f)
114
117
115 for f in s.added:
118 for f in s.added:
116 if ds[f] == 'r':
119 if ds[f] == 'r':
117 # added + removed -> unknown
120 # added + removed -> unknown
118 ds.drop(f)
121 ds.drop(f)
119 elif ds[f] != 'a':
122 elif ds[f] != 'a':
120 ds.add(f)
123 ds.add(f)
121
124
122 for f in s.removed:
125 for f in s.removed:
123 if ds[f] == 'a':
126 if ds[f] == 'a':
124 # removed + added -> normal
127 # removed + added -> normal
125 ds.normallookup(f)
128 ds.normallookup(f)
126 elif ds[f] != 'r':
129 elif ds[f] != 'r':
127 ds.remove(f)
130 ds.remove(f)
128
131
129 # Merge old parent and old working dir copies
132 # Merge old parent and old working dir copies
130 oldcopies = copiesmod.pathcopies(newctx, oldctx, match)
133 oldcopies = copiesmod.pathcopies(newctx, oldctx, match)
131 oldcopies.update(copies)
134 oldcopies.update(copies)
132 copies = dict((dst, oldcopies.get(src, src))
135 copies = dict((dst, oldcopies.get(src, src))
133 for dst, src in oldcopies.iteritems())
136 for dst, src in oldcopies.iteritems())
134 # Adjust the dirstate copies
137 # Adjust the dirstate copies
135 for dst, src in copies.iteritems():
138 for dst, src in copies.iteritems():
136 if (src not in newctx or dst in newctx or ds[dst] != 'a'):
139 if (src not in newctx or dst in newctx or ds[dst] != 'a'):
137 src = None
140 src = None
138 ds.copy(src, dst)
141 ds.copy(src, dst)
139
142
140
143
141 def _uncommitdirstate(repo, oldctx, match, interactive):
144 def _uncommitdirstate(repo, oldctx, match, interactive):
142 """Fix the dirstate after switching the working directory from
145 """Fix the dirstate after switching the working directory from
143 oldctx to a copy of oldctx not containing changed files matched by
146 oldctx to a copy of oldctx not containing changed files matched by
144 match.
147 match.
145 """
148 """
146 ctx = repo['.']
149 ctx = repo['.']
147 ds = repo.dirstate
150 ds = repo.dirstate
148 copies = dict(ds.copies())
151 copies = dict(ds.copies())
149 if interactive:
152 if interactive:
150 # In interactive cases, we will find the status between oldctx and ctx
153 # In interactive cases, we will find the status between oldctx and ctx
151 # and considering only the files which are changed between oldctx and
154 # and considering only the files which are changed between oldctx and
152 # ctx, and the status of what changed between oldctx and ctx will help
155 # ctx, and the status of what changed between oldctx and ctx will help
153 # us in defining the exact behavior
156 # us in defining the exact behavior
154 m, a, r = repo.status(oldctx, ctx, match=match)[:3]
157 m, a, r = repo.status(oldctx, ctx, match=match)[:3]
155 for f in m:
158 for f in m:
156 # These are files which are modified between oldctx and ctx which
159 # These are files which are modified between oldctx and ctx which
157 # contains two cases: 1) Were modified in oldctx and some
160 # contains two cases: 1) Were modified in oldctx and some
158 # modifications are uncommitted
161 # modifications are uncommitted
159 # 2) Were added in oldctx but some part is uncommitted (this cannot
162 # 2) Were added in oldctx but some part is uncommitted (this cannot
160 # contain the case when added files are uncommitted completely as
163 # contain the case when added files are uncommitted completely as
161 # that will result in status as removed not modified.)
164 # that will result in status as removed not modified.)
162 # Also any modifications to a removed file will result the status as
165 # Also any modifications to a removed file will result the status as
163 # added, so we have only two cases. So in either of the cases, the
166 # added, so we have only two cases. So in either of the cases, the
164 # resulting status can be modified or clean.
167 # resulting status can be modified or clean.
165 if ds[f] == 'r':
168 if ds[f] == 'r':
166 # But the file is removed in the working directory, leaving that
169 # But the file is removed in the working directory, leaving that
167 # as removed
170 # as removed
168 continue
171 continue
169 ds.normallookup(f)
172 ds.normallookup(f)
170
173
171 for f in a:
174 for f in a:
172 # These are the files which are added between oldctx and ctx(new
175 # These are the files which are added between oldctx and ctx(new
173 # one), which means the files which were removed in oldctx
176 # one), which means the files which were removed in oldctx
174 # but uncommitted completely while making the ctx
177 # but uncommitted completely while making the ctx
175 # This file should be marked as removed if the working directory
178 # This file should be marked as removed if the working directory
176 # does not adds it back. If it's adds it back, we do a normallookup.
179 # does not adds it back. If it's adds it back, we do a normallookup.
177 # The file can't be removed in working directory, because it was
180 # The file can't be removed in working directory, because it was
178 # removed in oldctx
181 # removed in oldctx
179 if ds[f] == 'a':
182 if ds[f] == 'a':
180 ds.normallookup(f)
183 ds.normallookup(f)
181 continue
184 continue
182 ds.remove(f)
185 ds.remove(f)
183
186
184 for f in r:
187 for f in r:
185 # These are files which are removed between oldctx and ctx, which
188 # These are files which are removed between oldctx and ctx, which
186 # means the files which were added in oldctx and were completely
189 # means the files which were added in oldctx and were completely
187 # uncommitted in ctx. If a added file is partially uncommitted, that
190 # uncommitted in ctx. If a added file is partially uncommitted, that
188 # would have resulted in modified status, not removed.
191 # would have resulted in modified status, not removed.
189 # So a file added in a commit, and uncommitting that addition must
192 # So a file added in a commit, and uncommitting that addition must
190 # result in file being stated as unknown.
193 # result in file being stated as unknown.
191 if ds[f] == 'r':
194 if ds[f] == 'r':
192 # The working directory say it's removed, so lets make the file
195 # The working directory say it's removed, so lets make the file
193 # unknown
196 # unknown
194 ds.drop(f)
197 ds.drop(f)
195 continue
198 continue
196 ds.add(f)
199 ds.add(f)
197 else:
200 else:
198 m, a, r = repo.status(oldctx.p1(), oldctx, match=match)[:3]
201 m, a, r = repo.status(oldctx.p1(), oldctx, match=match)[:3]
199 for f in m:
202 for f in m:
200 if ds[f] == 'r':
203 if ds[f] == 'r':
201 # modified + removed -> removed
204 # modified + removed -> removed
202 continue
205 continue
203 ds.normallookup(f)
206 ds.normallookup(f)
204
207
205 for f in a:
208 for f in a:
206 if ds[f] == 'r':
209 if ds[f] == 'r':
207 # added + removed -> unknown
210 # added + removed -> unknown
208 ds.drop(f)
211 ds.drop(f)
209 elif ds[f] != 'a':
212 elif ds[f] != 'a':
210 ds.add(f)
213 ds.add(f)
211
214
212 for f in r:
215 for f in r:
213 if ds[f] == 'a':
216 if ds[f] == 'a':
214 # removed + added -> normal
217 # removed + added -> normal
215 ds.normallookup(f)
218 ds.normallookup(f)
216 elif ds[f] != 'r':
219 elif ds[f] != 'r':
217 ds.remove(f)
220 ds.remove(f)
218
221
219 # Merge old parent and old working dir copies
222 # Merge old parent and old working dir copies
220 oldcopies = {}
223 oldcopies = {}
221 if interactive:
224 if interactive:
222 # Interactive had different meaning of the variables so restoring the
225 # Interactive had different meaning of the variables so restoring the
223 # original meaning to use them
226 # original meaning to use them
224 m, a, r = repo.status(oldctx.p1(), oldctx, match=match)[:3]
227 m, a, r = repo.status(oldctx.p1(), oldctx, match=match)[:3]
225 for f in (m + a):
228 for f in (m + a):
226 src = oldctx[f].renamed()
229 src = oldctx[f].renamed()
227 if src:
230 if src:
228 oldcopies[f] = src[0]
231 oldcopies[f] = src[0]
229 oldcopies.update(copies)
232 oldcopies.update(copies)
230 copies = dict((dst, oldcopies.get(src, src))
233 copies = dict((dst, oldcopies.get(src, src))
231 for dst, src in oldcopies.iteritems())
234 for dst, src in oldcopies.iteritems())
232 # Adjust the dirstate copies
235 # Adjust the dirstate copies
233 for dst, src in copies.iteritems():
236 for dst, src in copies.iteritems():
234 if (src not in ctx or dst in ctx or ds[dst] != 'a'):
237 if (src not in ctx or dst in ctx or ds[dst] != 'a'):
235 src = None
238 src = None
236 ds.copy(src, dst)
239 ds.copy(src, dst)
237
240
238 @command('uncommit',
241 @command('uncommit',
239 [('i', 'interactive', False, _('interactive mode to uncommit')),
242 [('i', 'interactive', False, _('interactive mode to uncommit')),
240 ('', 'keep', False, _('allow an empty commit after uncommiting')),
243 ('', 'keep', False, _('allow an empty commit after uncommiting')),
241 ] + commands.walkopts,
244 ] + commands.walkopts,
242 _('[OPTION]... [FILE]...'),
245 _('[OPTION]... [FILE]...'),
243 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
246 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
244 def uncommit(ui, repo, *pats, **opts):
247 def uncommit(ui, repo, *pats, **opts):
245 """uncommit part or all of a local changeset
248 """uncommit part or all of a local changeset
246
249
247 This command undoes the effect of a local commit, returning the affected
250 This command undoes the effect of a local commit, returning the affected
248 files to their uncommitted state. This means that files modified or
251 files to their uncommitted state. This means that files modified or
249 deleted in the changeset will be left unchanged, and so will remain
252 deleted in the changeset will be left unchanged, and so will remain
250 modified in the working directory.
253 modified in the working directory.
251
254
252 If no files are specified, the commit will be pruned, unless --keep is
255 If no files are specified, the commit will be pruned, unless --keep is
253 given.
256 given.
254 """
257 """
255 opts = pycompat.byteskwargs(opts)
258 opts = pycompat.byteskwargs(opts)
256 interactive = opts.get('interactive')
259 interactive = opts.get('interactive')
257
260
258 with repo.wlock(), repo.lock():
261 with repo.wlock(), repo.lock():
259
262
260 if not pats and not repo.ui.configbool('experimental',
263 if not pats and not repo.ui.configbool('experimental',
261 'uncommitondirtywdir'):
264 'uncommitondirtywdir'):
262 cmdutil.bailifchanged(repo)
265 cmdutil.bailifchanged(repo)
263 old = repo['.']
266 old = repo['.']
264 rewriteutil.precheck(repo, [old.rev()], 'uncommit')
267 rewriteutil.precheck(repo, [old.rev()], 'uncommit')
265 if len(old.parents()) > 1:
268 if len(old.parents()) > 1:
266 raise error.Abort(_("cannot uncommit merge changeset"))
269 raise error.Abort(_("cannot uncommit merge changeset"))
267
270
268 with repo.transaction('uncommit'):
271 with repo.transaction('uncommit'):
269 match = scmutil.match(old, pats, opts)
272 match = scmutil.match(old, pats, opts)
270 keepcommit = opts.get('keep') or pats
273 keepcommit = opts.get('keep') or pats
271 newid = _commitfiltered(repo, old, match, keepcommit)
274 newid = _commitfiltered(repo, old, match, keepcommit)
272 if interactive:
275 if interactive:
273 match = scmutil.match(old, pats, opts)
276 match = scmutil.match(old, pats, opts)
274 newid = _interactiveuncommit(ui, repo, old, match)
277 newid = _interactiveuncommit(ui, repo, old, match)
275
278
276 if newid is None:
279 if newid is None:
277 ui.status(_("nothing to uncommit\n"))
280 ui.status(_("nothing to uncommit\n"))
278 return 1
281 return 1
279
282
280 mapping = {}
283 mapping = {}
281 if newid != old.p1().node():
284 if newid != old.p1().node():
282 # Move local changes on filtered changeset
285 # Move local changes on filtered changeset
283 mapping[old.node()] = (newid,)
286 mapping[old.node()] = (newid,)
284 else:
287 else:
285 # Fully removed the old commit
288 # Fully removed the old commit
286 mapping[old.node()] = ()
289 mapping[old.node()] = ()
287
290
288 scmutil.cleanupnodes(repo, mapping, 'uncommit', fixphase=True)
291 scmutil.cleanupnodes(repo, mapping, 'uncommit', fixphase=True)
289
292
290 with repo.dirstate.parentchange():
293 with repo.dirstate.parentchange():
291 repo.dirstate.setparents(newid, node.nullid)
294 repo.dirstate.setparents(newid, node.nullid)
292 _uncommitdirstate(repo, old, match, interactive)
295 _uncommitdirstate(repo, old, match, interactive)
293
296
294 def _interactiveuncommit(ui, repo, old, match):
297 def _interactiveuncommit(ui, repo, old, match):
295 """ The function which contains all the logic for interactively uncommiting
298 """ The function which contains all the logic for interactively uncommiting
296 a commit. This function makes a temporary commit with the chunks which user
299 a commit. This function makes a temporary commit with the chunks which user
297 selected to uncommit. After that the diff of the parent and that commit is
300 selected to uncommit. After that the diff of the parent and that commit is
298 applied to the working directory and committed again which results in the
301 applied to the working directory and committed again which results in the
299 new commit which should be one after uncommitted.
302 new commit which should be one after uncommitted.
300 """
303 """
301
304
302 # create a temporary commit with hunks user selected
305 # create a temporary commit with hunks user selected
303 tempnode = _createtempcommit(ui, repo, old, match)
306 tempnode = _createtempcommit(ui, repo, old, match)
304
307
305 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
308 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
306 diffopts.nodates = True
309 diffopts.nodates = True
307 diffopts.git = True
310 diffopts.git = True
308 fp = stringio()
311 fp = stringio()
309 for chunk, label in patch.diffui(repo, tempnode, old.node(), None,
312 for chunk, label in patch.diffui(repo, tempnode, old.node(), None,
310 opts=diffopts):
313 opts=diffopts):
311 fp.write(chunk)
314 fp.write(chunk)
312
315
313 fp.seek(0)
316 fp.seek(0)
314 newnode = _patchtocommit(ui, repo, old, fp)
317 newnode = _patchtocommit(ui, repo, old, fp)
315 # creating obs marker temp -> ()
318 # creating obs marker temp -> ()
316 obsolete.createmarkers(repo, [(repo[tempnode], ())], operation="uncommit")
319 obsolete.createmarkers(repo, [(repo[tempnode], ())], operation="uncommit")
317 return newnode
320 return newnode
318
321
319 def _createtempcommit(ui, repo, old, match):
322 def _createtempcommit(ui, repo, old, match):
320 """ Creates a temporary commit for `uncommit --interative` which contains
323 """ Creates a temporary commit for `uncommit --interative` which contains
321 the hunks which were selected by the user to uncommit.
324 the hunks which were selected by the user to uncommit.
322 """
325 """
323
326
324 pold = old.p1()
327 pold = old.p1()
325 # The logic to interactively selecting something copied from
328 # The logic to interactively selecting something copied from
326 # cmdutil.revert()
329 # cmdutil.revert()
327 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
330 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
328 diffopts.nodates = True
331 diffopts.nodates = True
329 diffopts.git = True
332 diffopts.git = True
330 diff = patch.diff(repo, pold.node(), old.node(), match, opts=diffopts)
333 diff = patch.diff(repo, pold.node(), old.node(), match, opts=diffopts)
331 originalchunks = patch.parsepatch(diff)
334 originalchunks = patch.parsepatch(diff)
332 # XXX: The interactive selection is buggy and does not let you
335 # XXX: The interactive selection is buggy and does not let you
333 # uncommit a removed file partially.
336 # uncommit a removed file partially.
334 # TODO: wrap the operations in mercurial/patch.py and mercurial/crecord.py
337 # TODO: wrap the operations in mercurial/patch.py and mercurial/crecord.py
335 # to add uncommit as an operation taking care of BC.
338 # to add uncommit as an operation taking care of BC.
336 chunks, opts = cmdutil.recordfilter(repo.ui, originalchunks,
339 chunks, opts = cmdutil.recordfilter(repo.ui, originalchunks,
337 operation='discard')
340 operation='discard')
338 if not chunks:
341 if not chunks:
339 raise error.Abort(_("nothing selected to uncommit"))
342 raise error.Abort(_("nothing selected to uncommit"))
340 fp = stringio()
343 fp = stringio()
341 for c in chunks:
344 for c in chunks:
342 c.write(fp)
345 c.write(fp)
343
346
344 fp.seek(0)
347 fp.seek(0)
345 oldnode = node.hex(old.node())[:12]
348 oldnode = node.hex(old.node())[:12]
346 message = 'temporary commit for uncommiting %s' % oldnode
349 message = 'temporary commit for uncommiting %s' % oldnode
347 tempnode = _patchtocommit(ui, repo, old, fp, message, oldnode)
350 tempnode = _patchtocommit(ui, repo, old, fp, message, oldnode)
348 return tempnode
351 return tempnode
349
352
350 def _patchtocommit(ui, repo, old, fp, message=None, extras=None):
353 def _patchtocommit(ui, repo, old, fp, message=None, extras=None):
351 """ A function which will apply the patch to the working directory and
354 """ A function which will apply the patch to the working directory and
352 make a commit whose parents are same as that of old argument. The message
355 make a commit whose parents are same as that of old argument. The message
353 argument tells us whether to use the message of the old commit or a
356 argument tells us whether to use the message of the old commit or a
354 different message which is passed. Returns the node of new commit made.
357 different message which is passed. Returns the node of new commit made.
355 """
358 """
356 pold = old.p1()
359 pold = old.p1()
357 parents = (old.p1().node(), old.p2().node())
360 parents = (old.p1().node(), old.p2().node())
358 date = old.date()
361 date = old.date()
359 branch = old.branch()
362 branch = old.branch()
360 user = old.user()
363 user = old.user()
361 extra = old.extra()
364 extra = old.extra()
362 if extras:
365 if extras:
363 extra['uncommit_source'] = extras
366 extra['uncommit_source'] = extras
364 if not message:
367 if not message:
365 message = old.description()
368 message = old.description()
366 store = patch.filestore()
369 store = patch.filestore()
367 try:
370 try:
368 files = set()
371 files = set()
369 try:
372 try:
370 patch.patchrepo(ui, repo, pold, store, fp, 1, '',
373 patch.patchrepo(ui, repo, pold, store, fp, 1, '',
371 files=files, eolmode=None)
374 files=files, eolmode=None)
372 except patch.PatchError as err:
375 except patch.PatchError as err:
373 raise error.Abort(str(err))
376 raise error.Abort(str(err))
374
377
375 finally:
378 finally:
376 del fp
379 del fp
377
380
378 memctx = context.memctx(repo, parents, message, files=files,
381 memctx = context.memctx(repo, parents, message, files=files,
379 filectxfn=store,
382 filectxfn=store,
380 user=user,
383 user=user,
381 date=date,
384 date=date,
382 branch=branch,
385 branch=branch,
383 extra=extra)
386 extra=extra)
384 newcm = memctx.commit()
387 newcm = memctx.commit()
385 finally:
388 finally:
386 store.close()
389 store.close()
387 return newcm
390 return newcm
388
391
389 def predecessormarkers(ctx):
392 def predecessormarkers(ctx):
390 """yields the obsolete markers marking the given changeset as a successor"""
393 """yields the obsolete markers marking the given changeset as a successor"""
391 for data in ctx.repo().obsstore.predecessors.get(ctx.node(), ()):
394 for data in ctx.repo().obsstore.predecessors.get(ctx.node(), ()):
392 yield obsutil.marker(ctx.repo(), data)
395 yield obsutil.marker(ctx.repo(), data)
393
396
394 @command('unamend', [], helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
397 @command('unamend', [], helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
395 helpbasic=True)
398 helpbasic=True)
396 def unamend(ui, repo, **opts):
399 def unamend(ui, repo, **opts):
397 """undo the most recent amend operation on a current changeset
400 """undo the most recent amend operation on a current changeset
398
401
399 This command will roll back to the previous version of a changeset,
402 This command will roll back to the previous version of a changeset,
400 leaving working directory in state in which it was before running
403 leaving working directory in state in which it was before running
401 `hg amend` (e.g. files modified as part of an amend will be
404 `hg amend` (e.g. files modified as part of an amend will be
402 marked as modified `hg status`)
405 marked as modified `hg status`)
403 """
406 """
404
407
405 unfi = repo.unfiltered()
408 unfi = repo.unfiltered()
406 with repo.wlock(), repo.lock(), repo.transaction('unamend'):
409 with repo.wlock(), repo.lock(), repo.transaction('unamend'):
407
410
408 # identify the commit from which to unamend
411 # identify the commit from which to unamend
409 curctx = repo['.']
412 curctx = repo['.']
410
413
411 rewriteutil.precheck(repo, [curctx.rev()], 'unamend')
414 rewriteutil.precheck(repo, [curctx.rev()], 'unamend')
412
415
413 # identify the commit to which to unamend
416 # identify the commit to which to unamend
414 markers = list(predecessormarkers(curctx))
417 markers = list(predecessormarkers(curctx))
415 if len(markers) != 1:
418 if len(markers) != 1:
416 e = _("changeset must have one predecessor, found %i predecessors")
419 e = _("changeset must have one predecessor, found %i predecessors")
417 raise error.Abort(e % len(markers))
420 raise error.Abort(e % len(markers))
418
421
419 prednode = markers[0].prednode()
422 prednode = markers[0].prednode()
420 predctx = unfi[prednode]
423 predctx = unfi[prednode]
421
424
422 # add an extra so that we get a new hash
425 # add an extra so that we get a new hash
423 # note: allowing unamend to undo an unamend is an intentional feature
426 # note: allowing unamend to undo an unamend is an intentional feature
424 extras = predctx.extra()
427 extras = predctx.extra()
425 extras['unamend_source'] = curctx.hex()
428 extras['unamend_source'] = curctx.hex()
426
429
427 def filectxfn(repo, ctx_, path):
430 def filectxfn(repo, ctx_, path):
428 try:
431 try:
429 return predctx.filectx(path)
432 return predctx.filectx(path)
430 except KeyError:
433 except KeyError:
431 return None
434 return None
432
435
433 # Make a new commit same as predctx
436 # Make a new commit same as predctx
434 newctx = context.memctx(repo,
437 newctx = context.memctx(repo,
435 parents=(predctx.p1(), predctx.p2()),
438 parents=(predctx.p1(), predctx.p2()),
436 text=predctx.description(),
439 text=predctx.description(),
437 files=predctx.files(),
440 files=predctx.files(),
438 filectxfn=filectxfn,
441 filectxfn=filectxfn,
439 user=predctx.user(),
442 user=predctx.user(),
440 date=predctx.date(),
443 date=predctx.date(),
441 extra=extras)
444 extra=extras)
442 newprednode = repo.commitctx(newctx)
445 newprednode = repo.commitctx(newctx)
443 newpredctx = repo[newprednode]
446 newpredctx = repo[newprednode]
444 dirstate = repo.dirstate
447 dirstate = repo.dirstate
445
448
446 with dirstate.parentchange():
449 with dirstate.parentchange():
447 _fixdirstate(repo, curctx, newpredctx)
450 _fixdirstate(repo, curctx, newpredctx)
448
451
449 mapping = {curctx.node(): (newprednode,)}
452 mapping = {curctx.node(): (newprednode,)}
450 scmutil.cleanupnodes(repo, mapping, 'unamend', fixphase=True)
453 scmutil.cleanupnodes(repo, mapping, 'unamend', fixphase=True)
@@ -1,441 +1,444 b''
1 Test uncommit - set up the config
1 Test uncommit - set up the config
2
2
3 $ cat >> $HGRCPATH <<EOF
3 $ cat >> $HGRCPATH <<EOF
4 > [experimental]
4 > [experimental]
5 > evolution.createmarkers=True
5 > evolution.createmarkers=True
6 > evolution.allowunstable=True
6 > evolution.allowunstable=True
7 > [extensions]
7 > [extensions]
8 > uncommit =
8 > uncommit =
9 > drawdag=$TESTDIR/drawdag.py
9 > drawdag=$TESTDIR/drawdag.py
10 > EOF
10 > EOF
11
11
12 Build up a repo
12 Build up a repo
13
13
14 $ hg init repo
14 $ hg init repo
15 $ cd repo
15 $ cd repo
16 $ hg bookmark foo
16 $ hg bookmark foo
17
17
18 Help for uncommit
18 Help for uncommit
19
19
20 $ hg help uncommit
20 $ hg help uncommit
21 hg uncommit [OPTION]... [FILE]...
21 hg uncommit [OPTION]... [FILE]...
22
22
23 uncommit part or all of a local changeset
23 uncommit part or all of a local changeset
24
24
25 This command undoes the effect of a local commit, returning the affected
25 This command undoes the effect of a local commit, returning the affected
26 files to their uncommitted state. This means that files modified or
26 files to their uncommitted state. This means that files modified or
27 deleted in the changeset will be left unchanged, and so will remain
27 deleted in the changeset will be left unchanged, and so will remain
28 modified in the working directory.
28 modified in the working directory.
29
29
30 If no files are specified, the commit will be pruned, unless --keep is
30 If no files are specified, the commit will be pruned, unless --keep is
31 given.
31 given.
32
32
33 (use 'hg help -e uncommit' to show help for the uncommit extension)
33 (use 'hg help -e uncommit' to show help for the uncommit extension)
34
34
35 options ([+] can be repeated):
35 options ([+] can be repeated):
36
36
37 -i --interactive interactive mode to uncommit
37 -i --interactive interactive mode to uncommit
38 --keep allow an empty commit after uncommiting
38 --keep allow an empty commit after uncommiting
39 -I --include PATTERN [+] include names matching the given patterns
39 -I --include PATTERN [+] include names matching the given patterns
40 -X --exclude PATTERN [+] exclude names matching the given patterns
40 -X --exclude PATTERN [+] exclude names matching the given patterns
41
41
42 (some details hidden, use --verbose to show complete help)
42 (some details hidden, use --verbose to show complete help)
43
43
44 Uncommit with no commits should fail
44 Uncommit with no commits should fail
45
45
46 $ hg uncommit
46 $ hg uncommit
47 abort: cannot uncommit null changeset
47 abort: cannot uncommit null changeset
48 (no changeset checked out)
48 (no changeset checked out)
49 [255]
49 [255]
50
50
51 Create some commits
51 Create some commits
52
52
53 $ touch files
53 $ touch files
54 $ hg add files
54 $ hg add files
55 $ 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
55 $ 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
56 $ ls
56 $ ls
57 file-a
57 file-a
58 file-ab
58 file-ab
59 file-abc
59 file-abc
60 file-abcd
60 file-abcd
61 file-abcde
61 file-abcde
62 files
62 files
63
63
64 $ hg log -G -T '{rev}:{node} {desc}' --hidden
64 $ hg log -G -T '{rev}:{node} {desc}' --hidden
65 @ 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
65 @ 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
66 |
66 |
67 o 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
67 o 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
68 |
68 |
69 o 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
69 o 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
70 |
70 |
71 o 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
71 o 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
72 |
72 |
73 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
73 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
74
74
75 Simple uncommit off the top, also moves bookmark
75 Simple uncommit off the top, also moves bookmark
76
76
77 $ hg bookmark
77 $ hg bookmark
78 * foo 4:6c4fd43ed714
78 * foo 4:6c4fd43ed714
79 $ hg uncommit
79 $ hg uncommit
80 $ hg status
80 $ hg status
81 M files
81 M files
82 A file-abcde
82 A file-abcde
83 $ hg bookmark
83 $ hg bookmark
84 * foo 3:6db330d65db4
84 * foo 3:6db330d65db4
85
85
86 $ hg log -G -T '{rev}:{node} {desc}' --hidden
86 $ hg log -G -T '{rev}:{node} {desc}' --hidden
87 x 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
87 x 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
88 |
88 |
89 @ 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
89 @ 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
90 |
90 |
91 o 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
91 o 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
92 |
92 |
93 o 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
93 o 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
94 |
94 |
95 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
95 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
96
96
97
97
98 Recommit
98 Recommit
99
99
100 $ hg commit -m 'new change abcde'
100 $ hg commit -m 'new change abcde'
101 $ hg status
101 $ hg status
102 $ hg heads -T '{rev}:{node} {desc}'
102 $ hg heads -T '{rev}:{node} {desc}'
103 5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde (no-eol)
103 5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde (no-eol)
104
104
105 Uncommit of non-existent and unchanged files has no effect
105 Uncommit of non-existent and unchanged files has no effect
106 $ hg uncommit nothinghere
106 $ hg uncommit nothinghere
107 nothing to uncommit
107 nothing to uncommit
108 [1]
108 [1]
109 $ hg status
109 $ hg status
110 $ hg uncommit file-abc
110 $ hg uncommit file-abc
111 nothing to uncommit
111 nothing to uncommit
112 [1]
112 [1]
113 $ hg status
113 $ hg status
114
114
115 Try partial uncommit, also moves bookmark
115 Try partial uncommit, also moves bookmark
116
116
117 $ hg bookmark
117 $ hg bookmark
118 * foo 5:0c07a3ccda77
118 * foo 5:0c07a3ccda77
119 $ hg uncommit files
119 $ hg uncommit files
120 $ hg status
120 $ hg status
121 M files
121 M files
122 $ hg bookmark
122 $ hg bookmark
123 * foo 6:3727deee06f7
123 * foo 6:3727deee06f7
124 $ hg heads -T '{rev}:{node} {desc}'
124 $ hg heads -T '{rev}:{node} {desc}'
125 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde (no-eol)
125 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde (no-eol)
126 $ hg log -r . -p -T '{rev}:{node} {desc}'
126 $ hg log -r . -p -T '{rev}:{node} {desc}'
127 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcdediff -r 6db330d65db4 -r 3727deee06f7 file-abcde
127 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcdediff -r 6db330d65db4 -r 3727deee06f7 file-abcde
128 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
128 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
129 +++ b/file-abcde Thu Jan 01 00:00:00 1970 +0000
129 +++ b/file-abcde Thu Jan 01 00:00:00 1970 +0000
130 @@ -0,0 +1,1 @@
130 @@ -0,0 +1,1 @@
131 +abcde
131 +abcde
132
132
133 $ hg log -G -T '{rev}:{node} {desc}' --hidden
133 $ hg log -G -T '{rev}:{node} {desc}' --hidden
134 @ 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde
134 @ 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde
135 |
135 |
136 | x 5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde
136 | x 5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde
137 |/
137 |/
138 | x 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
138 | x 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
139 |/
139 |/
140 o 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
140 o 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
141 |
141 |
142 o 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
142 o 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
143 |
143 |
144 o 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
144 o 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
145 |
145 |
146 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
146 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
147
147
148 $ hg commit -m 'update files for abcde'
148 $ hg commit -m 'update files for abcde'
149
149
150 Uncommit with dirty state
150 Uncommit with dirty state
151
151
152 $ echo "foo" >> files
152 $ echo "foo" >> files
153 $ cat files
153 $ cat files
154 abcde
154 abcde
155 foo
155 foo
156 $ hg status
156 $ hg status
157 M files
157 M files
158 $ hg uncommit
158 $ hg uncommit
159 abort: uncommitted changes
159 abort: uncommitted changes
160 [255]
160 [255]
161 $ hg uncommit files
161 $ hg uncommit files
162 note: keeping empty commit
162 $ cat files
163 $ cat files
163 abcde
164 abcde
164 foo
165 foo
165 $ hg commit --amend -m "files abcde + foo"
166 $ hg commit --amend -m "files abcde + foo"
166
167
167 Testing the 'experimental.uncommitondirtywdir' config
168 Testing the 'experimental.uncommitondirtywdir' config
168
169
169 $ echo "bar" >> files
170 $ echo "bar" >> files
170 $ hg uncommit
171 $ hg uncommit
171 abort: uncommitted changes
172 abort: uncommitted changes
172 [255]
173 [255]
173 $ hg uncommit --config experimental.uncommitondirtywdir=True
174 $ hg uncommit --config experimental.uncommitondirtywdir=True
174 $ hg commit -m "files abcde + foo"
175 $ hg commit -m "files abcde + foo"
175
176
176 Uncommit in the middle of a stack, does not move bookmark
177 Uncommit in the middle of a stack, does not move bookmark
177
178
178 $ hg checkout '.^^^'
179 $ hg checkout '.^^^'
179 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
180 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
180 (leaving bookmark foo)
181 (leaving bookmark foo)
181 $ hg log -r . -p -T '{rev}:{node} {desc}'
182 $ hg log -r . -p -T '{rev}:{node} {desc}'
182 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abcdiff -r 69a232e754b0 -r abf2df566fc1 file-abc
183 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abcdiff -r 69a232e754b0 -r abf2df566fc1 file-abc
183 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
184 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
184 +++ b/file-abc Thu Jan 01 00:00:00 1970 +0000
185 +++ b/file-abc Thu Jan 01 00:00:00 1970 +0000
185 @@ -0,0 +1,1 @@
186 @@ -0,0 +1,1 @@
186 +abc
187 +abc
187 diff -r 69a232e754b0 -r abf2df566fc1 files
188 diff -r 69a232e754b0 -r abf2df566fc1 files
188 --- a/files Thu Jan 01 00:00:00 1970 +0000
189 --- a/files Thu Jan 01 00:00:00 1970 +0000
189 +++ b/files Thu Jan 01 00:00:00 1970 +0000
190 +++ b/files Thu Jan 01 00:00:00 1970 +0000
190 @@ -1,1 +1,1 @@
191 @@ -1,1 +1,1 @@
191 -ab
192 -ab
192 +abc
193 +abc
193
194
194 $ hg bookmark
195 $ hg bookmark
195 foo 10:48e5bd7cd583
196 foo 10:48e5bd7cd583
196 $ hg uncommit
197 $ hg uncommit
197 3 new orphan changesets
198 3 new orphan changesets
198 $ hg status
199 $ hg status
199 M files
200 M files
200 A file-abc
201 A file-abc
201 $ hg heads -T '{rev}:{node} {desc}'
202 $ hg heads -T '{rev}:{node} {desc}'
202 10:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo (no-eol)
203 10:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo (no-eol)
203 $ hg bookmark
204 $ hg bookmark
204 foo 10:48e5bd7cd583
205 foo 10:48e5bd7cd583
205 $ hg commit -m 'new abc'
206 $ hg commit -m 'new abc'
206 created new head
207 created new head
207
208
208 Partial uncommit in the middle, does not move bookmark
209 Partial uncommit in the middle, does not move bookmark
209
210
210 $ hg checkout '.^'
211 $ hg checkout '.^'
211 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
212 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
212 $ hg log -r . -p -T '{rev}:{node} {desc}'
213 $ hg log -r . -p -T '{rev}:{node} {desc}'
213 1:69a232e754b08d568c4899475faf2eb44b857802 added file-abdiff -r 3004d2d9b508 -r 69a232e754b0 file-ab
214 1:69a232e754b08d568c4899475faf2eb44b857802 added file-abdiff -r 3004d2d9b508 -r 69a232e754b0 file-ab
214 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
215 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
215 +++ b/file-ab Thu Jan 01 00:00:00 1970 +0000
216 +++ b/file-ab Thu Jan 01 00:00:00 1970 +0000
216 @@ -0,0 +1,1 @@
217 @@ -0,0 +1,1 @@
217 +ab
218 +ab
218 diff -r 3004d2d9b508 -r 69a232e754b0 files
219 diff -r 3004d2d9b508 -r 69a232e754b0 files
219 --- a/files Thu Jan 01 00:00:00 1970 +0000
220 --- a/files Thu Jan 01 00:00:00 1970 +0000
220 +++ b/files Thu Jan 01 00:00:00 1970 +0000
221 +++ b/files Thu Jan 01 00:00:00 1970 +0000
221 @@ -1,1 +1,1 @@
222 @@ -1,1 +1,1 @@
222 -a
223 -a
223 +ab
224 +ab
224
225
225 $ hg bookmark
226 $ hg bookmark
226 foo 10:48e5bd7cd583
227 foo 10:48e5bd7cd583
227 $ hg uncommit file-ab
228 $ hg uncommit file-ab
228 1 new orphan changesets
229 1 new orphan changesets
229 $ hg status
230 $ hg status
230 A file-ab
231 A file-ab
231
232
232 $ hg heads -T '{rev}:{node} {desc}\n'
233 $ hg heads -T '{rev}:{node} {desc}\n'
233 12:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
234 12:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
234 11:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
235 11:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
235 10:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
236 10:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
236
237
237 $ hg bookmark
238 $ hg bookmark
238 foo 10:48e5bd7cd583
239 foo 10:48e5bd7cd583
239 $ hg commit -m 'update ab'
240 $ hg commit -m 'update ab'
240 $ hg status
241 $ hg status
241 $ hg heads -T '{rev}:{node} {desc}\n'
242 $ hg heads -T '{rev}:{node} {desc}\n'
242 13:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
243 13:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
243 11:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
244 11:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
244 10:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
245 10:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
245
246
246 $ hg log -G -T '{rev}:{node} {desc}' --hidden
247 $ hg log -G -T '{rev}:{node} {desc}' --hidden
247 @ 13:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
248 @ 13:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
248 |
249 |
249 o 12:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
250 o 12:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
250 |
251 |
251 | * 11:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
252 | * 11:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
252 | |
253 | |
253 | | * 10:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
254 | | * 10:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
254 | | |
255 | | |
255 | | | x 9:8a6b58c173ca6a2e3745d8bd86698718d664bc6c files abcde + foo
256 | | | x 9:8a6b58c173ca6a2e3745d8bd86698718d664bc6c files abcde + foo
256 | | |/
257 | | |/
257 | | | x 8:39ad452c7f684a55d161c574340c5766c4569278 update files for abcde
258 | | | x 8:39ad452c7f684a55d161c574340c5766c4569278 update files for abcde
258 | | |/
259 | | |/
259 | | | x 7:0977fa602c2fd7d8427ed4e7ee15ea13b84c9173 update files for abcde
260 | | | x 7:0977fa602c2fd7d8427ed4e7ee15ea13b84c9173 update files for abcde
260 | | |/
261 | | |/
261 | | * 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde
262 | | * 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde
262 | | |
263 | | |
263 | | | x 5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde
264 | | | x 5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde
264 | | |/
265 | | |/
265 | | | x 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
266 | | | x 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
266 | | |/
267 | | |/
267 | | * 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
268 | | * 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
268 | | |
269 | | |
269 | | x 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
270 | | x 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
270 | |/
271 | |/
271 | x 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
272 | x 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
272 |/
273 |/
273 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
274 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
274
275
275 Uncommit with draft parent
276 Uncommit with draft parent
276
277
277 $ hg uncommit
278 $ hg uncommit
278 $ hg phase -r .
279 $ hg phase -r .
279 12: draft
280 12: draft
280 $ hg commit -m 'update ab again'
281 $ hg commit -m 'update ab again'
281
282
282 Phase is preserved
283 Phase is preserved
283
284
284 $ hg uncommit --keep --config phases.new-commit=secret
285 $ hg uncommit --keep --config phases.new-commit=secret
286 note: keeping empty commit
285 $ hg phase -r .
287 $ hg phase -r .
286 15: draft
288 15: draft
287 $ hg commit --amend -m 'update ab again'
289 $ hg commit --amend -m 'update ab again'
288
290
289 Uncommit with public parent
291 Uncommit with public parent
290
292
291 $ hg phase -p "::.^"
293 $ hg phase -p "::.^"
292 $ hg uncommit
294 $ hg uncommit
293 $ hg phase -r .
295 $ hg phase -r .
294 12: public
296 12: public
295
297
296 Partial uncommit with public parent
298 Partial uncommit with public parent
297
299
298 $ echo xyz > xyz
300 $ echo xyz > xyz
299 $ hg add xyz
301 $ hg add xyz
300 $ hg commit -m "update ab and add xyz"
302 $ hg commit -m "update ab and add xyz"
301 $ hg uncommit xyz
303 $ hg uncommit xyz
302 $ hg status
304 $ hg status
303 A xyz
305 A xyz
304 $ hg phase -r .
306 $ hg phase -r .
305 18: draft
307 18: draft
306 $ hg phase -r ".^"
308 $ hg phase -r ".^"
307 12: public
309 12: public
308
310
309 Uncommit leaving an empty changeset
311 Uncommit leaving an empty changeset
310
312
311 $ cd $TESTTMP
313 $ cd $TESTTMP
312 $ hg init repo1
314 $ hg init repo1
313 $ cd repo1
315 $ cd repo1
314 $ hg debugdrawdag <<'EOS'
316 $ hg debugdrawdag <<'EOS'
315 > Q
317 > Q
316 > |
318 > |
317 > P
319 > P
318 > EOS
320 > EOS
319 $ hg up Q -q
321 $ hg up Q -q
320 $ hg uncommit --keep
322 $ hg uncommit --keep
323 note: keeping empty commit
321 $ hg log -G -T '{desc} FILES: {files}'
324 $ hg log -G -T '{desc} FILES: {files}'
322 @ Q FILES:
325 @ Q FILES:
323 |
326 |
324 | x Q FILES: Q
327 | x Q FILES: Q
325 |/
328 |/
326 o P FILES: P
329 o P FILES: P
327
330
328 $ hg status
331 $ hg status
329 A Q
332 A Q
330
333
331 $ cd ..
334 $ cd ..
332 $ rm -rf repo1
335 $ rm -rf repo1
333
336
334 Testing uncommit while merge
337 Testing uncommit while merge
335
338
336 $ hg init repo2
339 $ hg init repo2
337 $ cd repo2
340 $ cd repo2
338
341
339 Create some history
342 Create some history
340
343
341 $ touch a
344 $ touch a
342 $ hg add a
345 $ hg add a
343 $ for i in 1 2 3; do echo $i > a; hg commit -m "a $i"; done
346 $ for i in 1 2 3; do echo $i > a; hg commit -m "a $i"; done
344 $ hg checkout 0
347 $ hg checkout 0
345 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
348 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
346 $ touch b
349 $ touch b
347 $ hg add b
350 $ hg add b
348 $ for i in 1 2 3; do echo $i > b; hg commit -m "b $i"; done
351 $ for i in 1 2 3; do echo $i > b; hg commit -m "b $i"; done
349 created new head
352 created new head
350 $ hg log -G -T '{rev}:{node} {desc}' --hidden
353 $ hg log -G -T '{rev}:{node} {desc}' --hidden
351 @ 5:2cd56cdde163ded2fbb16ba2f918c96046ab0bf2 b 3
354 @ 5:2cd56cdde163ded2fbb16ba2f918c96046ab0bf2 b 3
352 |
355 |
353 o 4:c3a0d5bb3b15834ffd2ef9ef603e93ec65cf2037 b 2
356 o 4:c3a0d5bb3b15834ffd2ef9ef603e93ec65cf2037 b 2
354 |
357 |
355 o 3:49bb009ca26078726b8870f1edb29fae8f7618f5 b 1
358 o 3:49bb009ca26078726b8870f1edb29fae8f7618f5 b 1
356 |
359 |
357 | o 2:990982b7384266e691f1bc08ca36177adcd1c8a9 a 3
360 | o 2:990982b7384266e691f1bc08ca36177adcd1c8a9 a 3
358 | |
361 | |
359 | o 1:24d38e3cf160c7b6f5ffe82179332229886a6d34 a 2
362 | o 1:24d38e3cf160c7b6f5ffe82179332229886a6d34 a 2
360 |/
363 |/
361 o 0:ea4e33293d4d274a2ba73150733c2612231f398c a 1
364 o 0:ea4e33293d4d274a2ba73150733c2612231f398c a 1
362
365
363
366
364 Add and expect uncommit to fail on both merge working dir and merge changeset
367 Add and expect uncommit to fail on both merge working dir and merge changeset
365
368
366 $ hg merge 2
369 $ hg merge 2
367 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
370 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
368 (branch merge, don't forget to commit)
371 (branch merge, don't forget to commit)
369
372
370 $ hg uncommit
373 $ hg uncommit
371 abort: outstanding uncommitted merge
374 abort: outstanding uncommitted merge
372 [255]
375 [255]
373
376
374 $ hg uncommit --config experimental.uncommitondirtywdir=True
377 $ hg uncommit --config experimental.uncommitondirtywdir=True
375 abort: cannot uncommit while merging
378 abort: cannot uncommit while merging
376 [255]
379 [255]
377
380
378 $ hg status
381 $ hg status
379 M a
382 M a
380 $ hg commit -m 'merge a and b'
383 $ hg commit -m 'merge a and b'
381
384
382 $ hg uncommit
385 $ hg uncommit
383 abort: cannot uncommit merge changeset
386 abort: cannot uncommit merge changeset
384 [255]
387 [255]
385
388
386 $ hg status
389 $ hg status
387 $ hg log -G -T '{rev}:{node} {desc}' --hidden
390 $ hg log -G -T '{rev}:{node} {desc}' --hidden
388 @ 6:c03b9c37bc67bf504d4912061cfb527b47a63c6e merge a and b
391 @ 6:c03b9c37bc67bf504d4912061cfb527b47a63c6e merge a and b
389 |\
392 |\
390 | o 5:2cd56cdde163ded2fbb16ba2f918c96046ab0bf2 b 3
393 | o 5:2cd56cdde163ded2fbb16ba2f918c96046ab0bf2 b 3
391 | |
394 | |
392 | o 4:c3a0d5bb3b15834ffd2ef9ef603e93ec65cf2037 b 2
395 | o 4:c3a0d5bb3b15834ffd2ef9ef603e93ec65cf2037 b 2
393 | |
396 | |
394 | o 3:49bb009ca26078726b8870f1edb29fae8f7618f5 b 1
397 | o 3:49bb009ca26078726b8870f1edb29fae8f7618f5 b 1
395 | |
398 | |
396 o | 2:990982b7384266e691f1bc08ca36177adcd1c8a9 a 3
399 o | 2:990982b7384266e691f1bc08ca36177adcd1c8a9 a 3
397 | |
400 | |
398 o | 1:24d38e3cf160c7b6f5ffe82179332229886a6d34 a 2
401 o | 1:24d38e3cf160c7b6f5ffe82179332229886a6d34 a 2
399 |/
402 |/
400 o 0:ea4e33293d4d274a2ba73150733c2612231f398c a 1
403 o 0:ea4e33293d4d274a2ba73150733c2612231f398c a 1
401
404
402
405
403 Rename a->b, then remove b in working copy. Result should remove a.
406 Rename a->b, then remove b in working copy. Result should remove a.
404
407
405 $ hg co -q 0
408 $ hg co -q 0
406 $ hg mv a b
409 $ hg mv a b
407 $ hg ci -qm 'move a to b'
410 $ hg ci -qm 'move a to b'
408 $ hg rm b
411 $ hg rm b
409 $ hg uncommit --config experimental.uncommitondirtywdir=True
412 $ hg uncommit --config experimental.uncommitondirtywdir=True
410 $ hg st --copies
413 $ hg st --copies
411 R a
414 R a
412 $ hg revert a
415 $ hg revert a
413
416
414 Rename a->b, then rename b->c in working copy. Result should rename a->c.
417 Rename a->b, then rename b->c in working copy. Result should rename a->c.
415
418
416 $ hg co -q 0
419 $ hg co -q 0
417 $ hg mv a b
420 $ hg mv a b
418 $ hg ci -qm 'move a to b'
421 $ hg ci -qm 'move a to b'
419 $ hg mv b c
422 $ hg mv b c
420 $ hg uncommit --config experimental.uncommitondirtywdir=True
423 $ hg uncommit --config experimental.uncommitondirtywdir=True
421 $ hg st --copies
424 $ hg st --copies
422 A c
425 A c
423 a
426 a
424 R a
427 R a
425 $ hg revert a
428 $ hg revert a
426 $ hg forget c
429 $ hg forget c
427 $ rm c
430 $ rm c
428
431
429 Copy a->b1 and a->b2, then rename b1->c in working copy. Result should copy a->b2 and a->c.
432 Copy a->b1 and a->b2, then rename b1->c in working copy. Result should copy a->b2 and a->c.
430
433
431 $ hg co -q 0
434 $ hg co -q 0
432 $ hg cp a b1
435 $ hg cp a b1
433 $ hg cp a b2
436 $ hg cp a b2
434 $ hg ci -qm 'move a to b1 and b2'
437 $ hg ci -qm 'move a to b1 and b2'
435 $ hg mv b1 c
438 $ hg mv b1 c
436 $ hg uncommit --config experimental.uncommitondirtywdir=True
439 $ hg uncommit --config experimental.uncommitondirtywdir=True
437 $ hg st --copies
440 $ hg st --copies
438 A b2
441 A b2
439 a
442 a
440 A c
443 A c
441 a
444 a
General Comments 0
You need to be logged in to leave comments. Login now