comparison hgext/histedit.py @ 17647:d34ba4991188

histedit: replaces patching logic by merges The old and fragile patching logic is replaced by smart merges (as rebase and graft do). This should prevents some conflicts and smoother human resolution. For this purpose the "foldchanges" function is renamed to "applychanges" and handle a single revision only.
author Pierre-Yves David <pierre-yves.david@ens-lyon.org>
date Fri, 21 Sep 2012 19:27:22 +0200
parents 4721fc933943
children 4f2390e3f4b0
comparison
equal deleted inserted replaced
17646:d44731a3adb8 17647:d34ba4991188
140 140
141 try: 141 try:
142 import cPickle as pickle 142 import cPickle as pickle
143 except ImportError: 143 except ImportError:
144 import pickle 144 import pickle
145 import tempfile
146 import os 145 import os
147 146
148 from mercurial import bookmarks 147 from mercurial import bookmarks
149 from mercurial import cmdutil 148 from mercurial import cmdutil
150 from mercurial import discovery 149 from mercurial import discovery
152 from mercurial import copies 151 from mercurial import copies
153 from mercurial import context 152 from mercurial import context
154 from mercurial import hg 153 from mercurial import hg
155 from mercurial import lock as lockmod 154 from mercurial import lock as lockmod
156 from mercurial import node 155 from mercurial import node
157 from mercurial import patch
158 from mercurial import repair 156 from mercurial import repair
159 from mercurial import scmutil 157 from mercurial import scmutil
160 from mercurial import util 158 from mercurial import util
159 from mercurial import merge as mergemod
161 from mercurial.i18n import _ 160 from mercurial.i18n import _
162 161
163 cmdtable = {} 162 cmdtable = {}
164 command = cmdutil.command(cmdtable) 163 command = cmdutil.command(cmdtable)
165 164
175 # d, drop = remove commit from history 174 # d, drop = remove commit from history
176 # m, mess = edit message without changing commit content 175 # m, mess = edit message without changing commit content
177 # 176 #
178 """) 177 """)
179 178
180 def foldchanges(ui, repo, node1, node2, opts): 179 def applychanges(ui, repo, ctx, opts):
181 """Produce a new changeset that represents the diff from node1 to node2.""" 180 """Merge changeset from ctx (only) in the current working directory"""
182 try: 181 wcpar = repo.dirstate.parents()[0]
183 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-') 182 if ctx.p1().node() == wcpar:
184 fp = os.fdopen(fd, 'w') 183 # edition ar "in place" we do not need to make any merge,
185 diffopts = patch.diffopts(ui, opts) 184 # just applies changes on parent for edition
186 diffopts.git = True 185 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
187 diffopts.ignorews = False 186 stats = None
188 diffopts.ignorewsamount = False 187 else:
189 diffopts.ignoreblanklines = False 188 try:
190 gen = patch.diff(repo, node1, node2, opts=diffopts) 189 # ui.forcemerge is an internal variable, do not document
191 for chunk in gen: 190 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
192 fp.write(chunk) 191 stats = mergemod.update(repo, ctx.node(), True, True, False,
193 fp.close() 192 ctx.p1().node())
194 files = set() 193 finally:
195 patch.patch(ui, repo, patchfile, files=files, eolmode=None) 194 repo.ui.setconfig('ui', 'forcemerge', '')
196 finally: 195 repo.setparents(wcpar, node.nullid)
197 os.unlink(patchfile) 196 repo.dirstate.write()
198 return files 197 # fix up dirstate for copies and renames
198 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
199 return stats
199 200
200 def collapse(repo, first, last, commitopts): 201 def collapse(repo, first, last, commitopts):
201 """collapse the set of revisions from first to last as new one. 202 """collapse the set of revisions from first to last as new one.
202 203
203 Expected commit options are: 204 Expected commit options are:
271 oldctx = repo[ha] 272 oldctx = repo[ha]
272 if oldctx.parents()[0] == ctx: 273 if oldctx.parents()[0] == ctx:
273 ui.debug('node %s unchanged\n' % ha) 274 ui.debug('node %s unchanged\n' % ha)
274 return oldctx, [], [], [] 275 return oldctx, [], [], []
275 hg.update(repo, ctx.node()) 276 hg.update(repo, ctx.node())
276 try: 277 stats = applychanges(ui, repo, oldctx, opts)
277 files = foldchanges(ui, repo, oldctx.p1().node() , ha, opts) 278 if stats and stats[3] > 0:
278 if not files:
279 ui.warn(_('%s: empty changeset')
280 % node.hex(ha))
281 return ctx, [], [], []
282 except Exception:
283 raise util.Abort(_('Fix up the change and run ' 279 raise util.Abort(_('Fix up the change and run '
284 'hg histedit --continue')) 280 'hg histedit --continue'))
281 # drop the second merge parent
285 n = repo.commit(text=oldctx.description(), user=oldctx.user(), 282 n = repo.commit(text=oldctx.description(), user=oldctx.user(),
286 date=oldctx.date(), extra=oldctx.extra()) 283 date=oldctx.date(), extra=oldctx.extra())
284 if n is None:
285 ui.warn(_('%s: empty changeset\n')
286 % node.hex(ha))
287 return ctx, [], [], []
287 return repo[n], [n], [oldctx.node()], [] 288 return repo[n], [n], [oldctx.node()], []
288 289
289 290
290 def edit(ui, repo, ctx, ha, opts): 291 def edit(ui, repo, ctx, ha, opts):
291 oldctx = repo[ha] 292 oldctx = repo[ha]
292 hg.update(repo, ctx.node()) 293 hg.update(repo, ctx.node())
293 try: 294 applychanges(ui, repo, oldctx, opts)
294 foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
295 except Exception:
296 pass
297 raise util.Abort(_('Make changes as needed, you may commit or record as ' 295 raise util.Abort(_('Make changes as needed, you may commit or record as '
298 'needed now.\nWhen you are finished, run hg' 296 'needed now.\nWhen you are finished, run hg'
299 ' histedit --continue to resume.')) 297 ' histedit --continue to resume.'))
300 298
301 def fold(ui, repo, ctx, ha, opts): 299 def fold(ui, repo, ctx, ha, opts):
302 oldctx = repo[ha] 300 oldctx = repo[ha]
303 hg.update(repo, ctx.node()) 301 hg.update(repo, ctx.node())
304 try: 302 stats = applychanges(ui, repo, oldctx, opts)
305 files = foldchanges(ui, repo, oldctx.p1().node() , ha, opts) 303 if stats and stats[3] > 0:
306 if not files:
307 ui.warn(_('%s: empty changeset')
308 % node.hex(ha))
309 return ctx, [], [], []
310 except Exception:
311 raise util.Abort(_('Fix up the change and run ' 304 raise util.Abort(_('Fix up the change and run '
312 'hg histedit --continue')) 305 'hg histedit --continue'))
313 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(), 306 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
314 date=oldctx.date(), extra=oldctx.extra()) 307 date=oldctx.date(), extra=oldctx.extra())
308 if n is None:
309 ui.warn(_('%s: empty changeset')
310 % node.hex(ha))
311 return ctx, [], [], []
315 return finishfold(ui, repo, ctx, oldctx, n, opts, []) 312 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
316 313
317 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges): 314 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
318 parent = ctx.parents()[0].node() 315 parent = ctx.parents()[0].node()
319 hg.update(repo, parent) 316 hg.update(repo, parent)
344 341
345 342
346 def message(ui, repo, ctx, ha, opts): 343 def message(ui, repo, ctx, ha, opts):
347 oldctx = repo[ha] 344 oldctx = repo[ha]
348 hg.update(repo, ctx.node()) 345 hg.update(repo, ctx.node())
349 try: 346 stats = applychanges(ui, repo, oldctx, opts)
350 foldchanges(ui, repo, oldctx.p1().node() , ha, opts) 347 if stats and stats[3] > 0:
351 except Exception:
352 raise util.Abort(_('Fix up the change and run ' 348 raise util.Abort(_('Fix up the change and run '
353 'hg histedit --continue')) 349 'hg histedit --continue'))
354 message = oldctx.description() + '\n' 350 message = oldctx.description() + '\n'
355 message = ui.edit(message, ui.username()) 351 message = ui.edit(message, ui.username())
356 new = repo.commit(text=message, user=oldctx.user(), date=oldctx.date(), 352 new = repo.commit(text=message, user=oldctx.user(), date=oldctx.date(),