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')