Mercurial > hg
comparison hgext/uncommit.py @ 35182:867990238dc6
unamend: move fb extension unamend to core
unamend extension adds an unamend command which undoes the effect of the amend
command. This patch moves the unamend command from that extension to uncommit
extension and this one does not completely undoes the effect of amend command as
it creates a new commit, rather than reviving the old one back.
This also adds tests for the same.
.. feature::
A new unamend command in uncommit extension which undoes the effect of the
amend command by creating a new changeset which was there before amend and
moving the changes that were amended to the working directory.
Differential Revision: https://phab.mercurial-scm.org/D821
author | Pulkit Goyal <7895pulkit@gmail.com> |
---|---|
date | Sun, 24 Sep 2017 00:56:52 +0530 |
parents | 3ebae3ec4664 |
children | 9dadcb99cc17 |
comparison
equal
deleted
inserted
replaced
35181:d4805a5e7e70 | 35182:867990238dc6 |
---|---|
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 |
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') |