##// END OF EJS Templates
histedit: move `between function` outside the action logic...
Pierre-Yves David -
r17642:bea381c1 default
parent child Browse files
Show More
@@ -1,670 +1,670
1 1 # histedit.py - interactive history editing for mercurial
2 2 #
3 3 # Copyright 2009 Augie Fackler <raf@durin42.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 """interactive history editing
8 8
9 9 With this extension installed, Mercurial gains one new command: histedit. Usage
10 10 is as follows, assuming the following history::
11 11
12 12 @ 3[tip] 7c2fd3b9020c 2009-04-27 18:04 -0500 durin42
13 13 | Add delta
14 14 |
15 15 o 2 030b686bedc4 2009-04-27 18:04 -0500 durin42
16 16 | Add gamma
17 17 |
18 18 o 1 c561b4e977df 2009-04-27 18:04 -0500 durin42
19 19 | Add beta
20 20 |
21 21 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
22 22 Add alpha
23 23
24 24 If you were to run ``hg histedit c561b4e977df``, you would see the following
25 25 file open in your editor::
26 26
27 27 pick c561b4e977df Add beta
28 28 pick 030b686bedc4 Add gamma
29 29 pick 7c2fd3b9020c Add delta
30 30
31 31 # Edit history between 633536316234 and 7c2fd3b9020c
32 32 #
33 33 # Commands:
34 34 # p, pick = use commit
35 35 # e, edit = use commit, but stop for amending
36 36 # f, fold = use commit, but fold into previous commit (combines N and N-1)
37 37 # d, drop = remove commit from history
38 38 # m, mess = edit message without changing commit content
39 39 #
40 40
41 41 In this file, lines beginning with ``#`` are ignored. You must specify a rule
42 42 for each revision in your history. For example, if you had meant to add gamma
43 43 before beta, and then wanted to add delta in the same revision as beta, you
44 44 would reorganize the file to look like this::
45 45
46 46 pick 030b686bedc4 Add gamma
47 47 pick c561b4e977df Add beta
48 48 fold 7c2fd3b9020c Add delta
49 49
50 50 # Edit history between 633536316234 and 7c2fd3b9020c
51 51 #
52 52 # Commands:
53 53 # p, pick = use commit
54 54 # e, edit = use commit, but stop for amending
55 55 # f, fold = use commit, but fold into previous commit (combines N and N-1)
56 56 # d, drop = remove commit from history
57 57 # m, mess = edit message without changing commit content
58 58 #
59 59
60 60 At which point you close the editor and ``histedit`` starts working. When you
61 61 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
62 62 those revisions together, offering you a chance to clean up the commit message::
63 63
64 64 Add beta
65 65 ***
66 66 Add delta
67 67
68 68 Edit the commit message to your liking, then close the editor. For
69 69 this example, let's assume that the commit message was changed to
70 70 ``Add beta and delta.`` After histedit has run and had a chance to
71 71 remove any old or temporary revisions it needed, the history looks
72 72 like this::
73 73
74 74 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
75 75 | Add beta and delta.
76 76 |
77 77 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
78 78 | Add gamma
79 79 |
80 80 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
81 81 Add alpha
82 82
83 83 Note that ``histedit`` does *not* remove any revisions (even its own temporary
84 84 ones) until after it has completed all the editing operations, so it will
85 85 probably perform several strip operations when it's done. For the above example,
86 86 it had to run strip twice. Strip can be slow depending on a variety of factors,
87 87 so you might need to be a little patient. You can choose to keep the original
88 88 revisions by passing the ``--keep`` flag.
89 89
90 90 The ``edit`` operation will drop you back to a command prompt,
91 91 allowing you to edit files freely, or even use ``hg record`` to commit
92 92 some changes as a separate commit. When you're done, any remaining
93 93 uncommitted changes will be committed as well. When done, run ``hg
94 94 histedit --continue`` to finish this step. You'll be prompted for a
95 95 new commit message, but the default commit message will be the
96 96 original message for the ``edit`` ed revision.
97 97
98 98 The ``message`` operation will give you a chance to revise a commit
99 99 message without changing the contents. It's a shortcut for doing
100 100 ``edit`` immediately followed by `hg histedit --continue``.
101 101
102 102 If ``histedit`` encounters a conflict when moving a revision (while
103 103 handling ``pick`` or ``fold``), it'll stop in a similar manner to
104 104 ``edit`` with the difference that it won't prompt you for a commit
105 105 message when done. If you decide at this point that you don't like how
106 106 much work it will be to rearrange history, or that you made a mistake,
107 107 you can use ``hg histedit --abort`` to abandon the new changes you
108 108 have made and return to the state before you attempted to edit your
109 109 history.
110 110
111 111 If we clone the example repository above and add three more changes, such that
112 112 we have the following history::
113 113
114 114 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
115 115 | Add theta
116 116 |
117 117 o 5 140988835471 2009-04-27 18:04 -0500 stefan
118 118 | Add eta
119 119 |
120 120 o 4 122930637314 2009-04-27 18:04 -0500 stefan
121 121 | Add zeta
122 122 |
123 123 o 3 836302820282 2009-04-27 18:04 -0500 stefan
124 124 | Add epsilon
125 125 |
126 126 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
127 127 | Add beta and delta.
128 128 |
129 129 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
130 130 | Add gamma
131 131 |
132 132 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
133 133 Add alpha
134 134
135 135 If you run ``hg histedit --outgoing`` on the clone then it is the same
136 136 as running ``hg histedit 836302820282``. If you need plan to push to a
137 137 repository that Mercurial does not detect to be related to the source
138 138 repo, you can add a ``--force`` option.
139 139 """
140 140
141 141 try:
142 142 import cPickle as pickle
143 143 except ImportError:
144 144 import pickle
145 145 import tempfile
146 146 import os
147 147
148 148 from mercurial import bookmarks
149 149 from mercurial import cmdutil
150 150 from mercurial import discovery
151 151 from mercurial import error
152 152 from mercurial import hg
153 153 from mercurial import lock as lockmod
154 154 from mercurial import node
155 155 from mercurial import patch
156 156 from mercurial import repair
157 157 from mercurial import scmutil
158 158 from mercurial import util
159 159 from mercurial.i18n import _
160 160
161 161 cmdtable = {}
162 162 command = cmdutil.command(cmdtable)
163 163
164 164 testedwith = 'internal'
165 165
166 166 # i18n: command names and abbreviations must remain untranslated
167 167 editcomment = _("""# Edit history between %s and %s
168 168 #
169 169 # Commands:
170 170 # p, pick = use commit
171 171 # e, edit = use commit, but stop for amending
172 172 # f, fold = use commit, but fold into previous commit (combines N and N-1)
173 173 # d, drop = remove commit from history
174 174 # m, mess = edit message without changing commit content
175 175 #
176 176 """)
177 177
178 178 def foldchanges(ui, repo, node1, node2, opts):
179 179 """Produce a new changeset that represents the diff from node1 to node2."""
180 180 try:
181 181 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
182 182 fp = os.fdopen(fd, 'w')
183 183 diffopts = patch.diffopts(ui, opts)
184 184 diffopts.git = True
185 185 diffopts.ignorews = False
186 186 diffopts.ignorewsamount = False
187 187 diffopts.ignoreblanklines = False
188 188 gen = patch.diff(repo, node1, node2, opts=diffopts)
189 189 for chunk in gen:
190 190 fp.write(chunk)
191 191 fp.close()
192 192 files = set()
193 193 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
194 194 finally:
195 195 os.unlink(patchfile)
196 196 return files
197 197
198 def between(repo, old, new, keep):
199 """select and validate the set of revision to edit
200
201 When keep is false, the specified set can't have children."""
202 revs = [old]
203 current = old
204 while current != new:
205 ctx = repo[current]
206 if not keep and len(ctx.children()) > 1:
207 raise util.Abort(_('cannot edit history that would orphan nodes'))
208 if len(ctx.parents()) != 1 and ctx.parents()[1] != node.nullid:
209 raise util.Abort(_("can't edit history with merges"))
210 if not ctx.children():
211 current = new
212 else:
213 current = ctx.children()[0].node()
214 revs.append(current)
215 if len(repo[current].children()) and not keep:
216 raise util.Abort(_('cannot edit history that would orphan nodes'))
217 return revs
218
219
220 198 def pick(ui, repo, ctx, ha, opts):
221 199 oldctx = repo[ha]
222 200 if oldctx.parents()[0] == ctx:
223 201 ui.debug('node %s unchanged\n' % ha)
224 202 return oldctx, [], [], []
225 203 hg.update(repo, ctx.node())
226 204 try:
227 205 files = foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
228 206 if not files:
229 207 ui.warn(_('%s: empty changeset')
230 208 % node.hex(ha))
231 209 return ctx, [], [], []
232 210 except Exception:
233 211 raise util.Abort(_('Fix up the change and run '
234 212 'hg histedit --continue'))
235 213 n = repo.commit(text=oldctx.description(), user=oldctx.user(),
236 214 date=oldctx.date(), extra=oldctx.extra())
237 215 return repo[n], [n], [oldctx.node()], []
238 216
239 217
240 218 def edit(ui, repo, ctx, ha, opts):
241 219 oldctx = repo[ha]
242 220 hg.update(repo, ctx.node())
243 221 try:
244 222 foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
245 223 except Exception:
246 224 pass
247 225 raise util.Abort(_('Make changes as needed, you may commit or record as '
248 226 'needed now.\nWhen you are finished, run hg'
249 227 ' histedit --continue to resume.'))
250 228
251 229 def fold(ui, repo, ctx, ha, opts):
252 230 oldctx = repo[ha]
253 231 hg.update(repo, ctx.node())
254 232 try:
255 233 files = foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
256 234 if not files:
257 235 ui.warn(_('%s: empty changeset')
258 236 % node.hex(ha))
259 237 return ctx, [], [], []
260 238 except Exception:
261 239 raise util.Abort(_('Fix up the change and run '
262 240 'hg histedit --continue'))
263 241 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
264 242 date=oldctx.date(), extra=oldctx.extra())
265 243 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
266 244
267 245 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
268 246 parent = ctx.parents()[0].node()
269 247 hg.update(repo, parent)
270 248 foldchanges(ui, repo, parent, newnode, opts)
271 249 newmessage = '\n***\n'.join(
272 250 [ctx.description()] +
273 251 [repo[r].description() for r in internalchanges] +
274 252 [oldctx.description()]) + '\n'
275 253 # If the changesets are from the same author, keep it.
276 254 if ctx.user() == oldctx.user():
277 255 username = ctx.user()
278 256 else:
279 257 username = ui.username()
280 258 newmessage = ui.edit(newmessage, username)
281 259 n = repo.commit(text=newmessage, user=username,
282 260 date=max(ctx.date(), oldctx.date()), extra=oldctx.extra())
283 261 return repo[n], [n], [oldctx.node(), ctx.node()], [newnode]
284 262
285 263 def drop(ui, repo, ctx, ha, opts):
286 264 return ctx, [], [repo[ha].node()], []
287 265
288 266
289 267 def message(ui, repo, ctx, ha, opts):
290 268 oldctx = repo[ha]
291 269 hg.update(repo, ctx.node())
292 270 try:
293 271 foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
294 272 except Exception:
295 273 raise util.Abort(_('Fix up the change and run '
296 274 'hg histedit --continue'))
297 275 message = oldctx.description() + '\n'
298 276 message = ui.edit(message, ui.username())
299 277 new = repo.commit(text=message, user=oldctx.user(), date=oldctx.date(),
300 278 extra=oldctx.extra())
301 279 newctx = repo[new]
302 280 if oldctx.node() != newctx.node():
303 281 return newctx, [new], [oldctx.node()], []
304 282 # We didn't make an edit, so just indicate no replaced nodes
305 283 return newctx, [new], [], []
306 284
307 285
308 286 def makedesc(c):
309 287 """build a initial action line for a ctx `c`
310 288
311 289 line are in the form:
312 290
313 291 pick <hash> <rev> <summary>
314 292 """
315 293 summary = ''
316 294 if c.description():
317 295 summary = c.description().splitlines()[0]
318 296 line = 'pick %s %d %s' % (c.hex()[:12], c.rev(), summary)
319 297 return line[:80] # trim to 80 chars so it's not stupidly wide in my editor
320 298
321 299 actiontable = {'p': pick,
322 300 'pick': pick,
323 301 'e': edit,
324 302 'edit': edit,
325 303 'f': fold,
326 304 'fold': fold,
327 305 'd': drop,
328 306 'drop': drop,
329 307 'm': message,
330 308 'mess': message,
331 309 }
332 310
333 311 @command('histedit',
334 312 [('', 'commands', '',
335 313 _('Read history edits from the specified file.')),
336 314 ('c', 'continue', False, _('continue an edit already in progress')),
337 315 ('k', 'keep', False,
338 316 _("don't strip old nodes after edit is complete")),
339 317 ('', 'abort', False, _('abort an edit in progress')),
340 318 ('o', 'outgoing', False, _('changesets not found in destination')),
341 319 ('f', 'force', False,
342 320 _('force outgoing even for unrelated repositories')),
343 321 ('r', 'rev', [], _('first revision to be edited'))],
344 322 _("[PARENT]"))
345 323 def histedit(ui, repo, *parent, **opts):
346 324 """interactively edit changeset history
347 325 """
348 326 # TODO only abort if we try and histedit mq patches, not just
349 327 # blanket if mq patches are applied somewhere
350 328 mq = getattr(repo, 'mq', None)
351 329 if mq and mq.applied:
352 330 raise util.Abort(_('source has mq patches applied'))
353 331
354 332 parent = list(parent) + opts.get('rev', [])
355 333 if opts.get('outgoing'):
356 334 if len(parent) > 1:
357 335 raise util.Abort(
358 336 _('only one repo argument allowed with --outgoing'))
359 337 elif parent:
360 338 parent = parent[0]
361 339
362 340 dest = ui.expandpath(parent or 'default-push', parent or 'default')
363 341 dest, revs = hg.parseurl(dest, None)[:2]
364 342 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
365 343
366 344 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
367 345 other = hg.peer(repo, opts, dest)
368 346
369 347 if revs:
370 348 revs = [repo.lookup(rev) for rev in revs]
371 349
372 350 parent = discovery.findcommonoutgoing(
373 351 repo, other, [], force=opts.get('force')).missing[0:1]
374 352 else:
375 353 if opts.get('force'):
376 354 raise util.Abort(_('--force only allowed with --outgoing'))
377 355
378 356 if opts.get('continue', False):
379 357 if len(parent) != 0:
380 358 raise util.Abort(_('no arguments allowed with --continue'))
381 359 (parentctxnode, created, replaced,
382 360 tmpnodes, existing, rules, keep, tip, replacemap) = readstate(repo)
383 361 currentparent, wantnull = repo.dirstate.parents()
384 362 parentctx = repo[parentctxnode]
385 363 # existing is the list of revisions initially considered by
386 364 # histedit. Here we use it to list new changesets, descendants
387 365 # of parentctx without an 'existing' changeset in-between. We
388 366 # also have to exclude 'existing' changesets which were
389 367 # previously dropped.
390 368 descendants = set(c.node() for c in
391 369 repo.set('(%n::) - %n', parentctxnode, parentctxnode))
392 370 existing = set(existing)
393 371 notdropped = set(n for n in existing if n in descendants and
394 372 (n not in replacemap or replacemap[n] in descendants))
395 373 # Discover any nodes the user has added in the interim. We can
396 374 # miss changesets which were dropped and recreated the same.
397 375 newchildren = list(c.node() for c in repo.set(
398 376 'sort(%ln - (%ln or %ln::))', descendants, existing, notdropped))
399 377 action, currentnode = rules.pop(0)
400 378 if action in ('f', 'fold'):
401 379 tmpnodes.extend(newchildren)
402 380 else:
403 381 created.extend(newchildren)
404 382
405 383 m, a, r, d = repo.status()[:4]
406 384 oldctx = repo[currentnode]
407 385 message = oldctx.description() + '\n'
408 386 if action in ('e', 'edit', 'm', 'mess'):
409 387 message = ui.edit(message, ui.username())
410 388 elif action in ('f', 'fold'):
411 389 message = 'fold-temp-revision %s' % currentnode
412 390 new = None
413 391 if m or a or r or d:
414 392 new = repo.commit(text=message, user=oldctx.user(),
415 393 date=oldctx.date(), extra=oldctx.extra())
416 394
417 395 # If we're resuming a fold and we have new changes, mark the
418 396 # replacements and finish the fold. If not, it's more like a
419 397 # drop of the changesets that disappeared, and we can skip
420 398 # this step.
421 399 if action in ('f', 'fold') and (new or newchildren):
422 400 if new:
423 401 tmpnodes.append(new)
424 402 else:
425 403 new = newchildren[-1]
426 404 (parentctx, created_, replaced_, tmpnodes_) = finishfold(
427 405 ui, repo, parentctx, oldctx, new, opts, newchildren)
428 406 replaced.extend(replaced_)
429 407 created.extend(created_)
430 408 tmpnodes.extend(tmpnodes_)
431 409 elif action not in ('d', 'drop'):
432 410 if new != oldctx.node():
433 411 replaced.append(oldctx.node())
434 412 if new:
435 413 if new != oldctx.node():
436 414 created.append(new)
437 415 parentctx = repo[new]
438 416
439 417 elif opts.get('abort', False):
440 418 if len(parent) != 0:
441 419 raise util.Abort(_('no arguments allowed with --abort'))
442 420 (parentctxnode, created, replaced, tmpnodes,
443 421 existing, rules, keep, tip, replacemap) = readstate(repo)
444 422 ui.debug('restore wc to old tip %s\n' % node.hex(tip))
445 423 hg.clean(repo, tip)
446 424 ui.debug('should strip created nodes %s\n' %
447 425 ', '.join([node.hex(n)[:12] for n in created]))
448 426 ui.debug('should strip temp nodes %s\n' %
449 427 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
450 428 for nodes in (created, tmpnodes):
451 429 lock = None
452 430 try:
453 431 lock = repo.lock()
454 432 for n in reversed(nodes):
455 433 try:
456 434 repair.strip(ui, repo, n)
457 435 except error.LookupError:
458 436 pass
459 437 finally:
460 438 lockmod.release(lock)
461 439 os.unlink(os.path.join(repo.path, 'histedit-state'))
462 440 return
463 441 else:
464 442 cmdutil.bailifchanged(repo)
465 443 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
466 444 raise util.Abort(_('history edit already in progress, try '
467 445 '--continue or --abort'))
468 446
469 447 tip, empty = repo.dirstate.parents()
470 448
471 449
472 450 if len(parent) != 1:
473 451 raise util.Abort(_('histedit requires exactly one parent revision'))
474 452 parent = scmutil.revsingle(repo, parent[0]).node()
475 453
476 454 keep = opts.get('keep', False)
477 455 revs = between(repo, parent, tip, keep)
478 456
479 457 ctxs = [repo[r] for r in revs]
480 458 existing = [r.node() for r in ctxs]
481 459 rules = opts.get('commands', '')
482 460 if not rules:
483 461 rules = '\n'.join([makedesc(c) for c in ctxs])
484 462 rules += '\n\n'
485 463 rules += editcomment % (node.hex(parent)[:12], node.hex(tip)[:12])
486 464 rules = ui.edit(rules, ui.username())
487 465 # Save edit rules in .hg/histedit-last-edit.txt in case
488 466 # the user needs to ask for help after something
489 467 # surprising happens.
490 468 f = open(repo.join('histedit-last-edit.txt'), 'w')
491 469 f.write(rules)
492 470 f.close()
493 471 else:
494 472 f = open(rules)
495 473 rules = f.read()
496 474 f.close()
497 475 rules = [l for l in (r.strip() for r in rules.splitlines())
498 476 if l and not l[0] == '#']
499 477 rules = verifyrules(rules, repo, ctxs)
500 478
501 479 parentctx = repo[parent].parents()[0]
502 480 keep = opts.get('keep', False)
503 481 replaced = []
504 482 replacemap = {}
505 483 tmpnodes = []
506 484 created = []
507 485
508 486
509 487 while rules:
510 488 writestate(repo, parentctx.node(), created, replaced,
511 489 tmpnodes, existing, rules, keep, tip, replacemap)
512 490 action, ha = rules.pop(0)
513 491 (parentctx, created_, replaced_, tmpnodes_) = actiontable[action](
514 492 ui, repo, parentctx, ha, opts)
515 493
516 494 if replaced_:
517 495 clen, rlen = len(created_), len(replaced_)
518 496 if clen == rlen == 1:
519 497 ui.debug('histedit: exact replacement of %s with %s\n' % (
520 498 node.short(replaced_[0]), node.short(created_[0])))
521 499
522 500 replacemap[replaced_[0]] = created_[0]
523 501 elif clen > rlen:
524 502 assert rlen == 1, ('unexpected replacement of '
525 503 '%d changes with %d changes' % (rlen, clen))
526 504 # made more changesets than we're replacing
527 505 # TODO synthesize patch names for created patches
528 506 replacemap[replaced_[0]] = created_[-1]
529 507 ui.debug('histedit: created many, assuming %s replaced by %s' %
530 508 (node.short(replaced_[0]), node.short(created_[-1])))
531 509 elif rlen > clen:
532 510 if not created_:
533 511 # This must be a drop. Try and put our metadata on
534 512 # the parent change.
535 513 assert rlen == 1
536 514 r = replaced_[0]
537 515 ui.debug('histedit: %s seems replaced with nothing, '
538 516 'finding a parent\n' % (node.short(r)))
539 517 pctx = repo[r].parents()[0]
540 518 if pctx.node() in replacemap:
541 519 ui.debug('histedit: parent is already replaced\n')
542 520 replacemap[r] = replacemap[pctx.node()]
543 521 else:
544 522 replacemap[r] = pctx.node()
545 523 ui.debug('histedit: %s best replaced by %s\n' % (
546 524 node.short(r), node.short(replacemap[r])))
547 525 else:
548 526 assert len(created_) == 1
549 527 for r in replaced_:
550 528 ui.debug('histedit: %s replaced by %s\n' % (
551 529 node.short(r), node.short(created_[0])))
552 530 replacemap[r] = created_[0]
553 531 else:
554 532 assert False, (
555 533 'Unhandled case in replacement mapping! '
556 534 'replacing %d changes with %d changes' % (rlen, clen))
557 535 created.extend(created_)
558 536 replaced.extend(replaced_)
559 537 tmpnodes.extend(tmpnodes_)
560 538
561 539 hg.update(repo, parentctx.node())
562 540
563 541 if not keep:
564 542 if replacemap:
565 543 ui.note(_('histedit: Should update metadata for the following '
566 544 'changes:\n'))
567 545
568 546 def copybms(old, new):
569 547 if old in tmpnodes or old in created:
570 548 # can't have any metadata we'd want to update
571 549 return
572 550 while new in replacemap:
573 551 new = replacemap[new]
574 552 ui.note(_('histedit: %s to %s\n') % (node.short(old),
575 553 node.short(new)))
576 554 octx = repo[old]
577 555 marks = octx.bookmarks()
578 556 if marks:
579 557 ui.note(_('histedit: moving bookmarks %s\n') %
580 558 ', '.join(marks))
581 559 for mark in marks:
582 560 repo._bookmarks[mark] = new
583 561 bookmarks.write(repo)
584 562
585 563 # We assume that bookmarks on the tip should remain
586 564 # tipmost, but bookmarks on non-tip changesets should go
587 565 # to their most reasonable successor. As a result, find
588 566 # the old tip and new tip and copy those bookmarks first,
589 567 # then do the rest of the bookmark copies.
590 568 oldtip = sorted(replacemap.keys(), key=repo.changelog.rev)[-1]
591 569 newtip = sorted(replacemap.values(), key=repo.changelog.rev)[-1]
592 570 copybms(oldtip, newtip)
593 571
594 572 for old, new in sorted(replacemap.iteritems()):
595 573 copybms(old, new)
596 574 # TODO update mq state
597 575
598 576 ui.debug('should strip replaced nodes %s\n' %
599 577 ', '.join([node.hex(n)[:12] for n in replaced]))
600 578 lock = None
601 579 try:
602 580 lock = repo.lock()
603 581 for n in sorted(replaced, key=lambda x: repo[x].rev()):
604 582 try:
605 583 repair.strip(ui, repo, n)
606 584 except error.LookupError:
607 585 pass
608 586 finally:
609 587 lockmod.release(lock)
610 588
611 589 ui.debug('should strip temp nodes %s\n' %
612 590 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
613 591 lock = None
614 592 try:
615 593 lock = repo.lock()
616 594 for n in reversed(tmpnodes):
617 595 try:
618 596 repair.strip(ui, repo, n)
619 597 except error.LookupError:
620 598 pass
621 599 finally:
622 600 lockmod.release(lock)
623 601 os.unlink(os.path.join(repo.path, 'histedit-state'))
624 602 if os.path.exists(repo.sjoin('undo')):
625 603 os.unlink(repo.sjoin('undo'))
626 604
627 605
606 def between(repo, old, new, keep):
607 """select and validate the set of revision to edit
608
609 When keep is false, the specified set can't have children."""
610 revs = [old]
611 current = old
612 while current != new:
613 ctx = repo[current]
614 if not keep and len(ctx.children()) > 1:
615 raise util.Abort(_('cannot edit history that would orphan nodes'))
616 if len(ctx.parents()) != 1 and ctx.parents()[1] != node.nullid:
617 raise util.Abort(_("can't edit history with merges"))
618 if not ctx.children():
619 current = new
620 else:
621 current = ctx.children()[0].node()
622 revs.append(current)
623 if len(repo[current].children()) and not keep:
624 raise util.Abort(_('cannot edit history that would orphan nodes'))
625 return revs
626
627
628 628 def writestate(repo, parentctxnode, created, replaced,
629 629 tmpnodes, existing, rules, keep, oldtip, replacemap):
630 630 fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
631 631 pickle.dump((parentctxnode, created, replaced,
632 632 tmpnodes, existing, rules, keep, oldtip, replacemap),
633 633 fp)
634 634 fp.close()
635 635
636 636 def readstate(repo):
637 637 """Returns a tuple of (parentnode, created, replaced, tmp, existing, rules,
638 638 keep, oldtip, replacemap ).
639 639 """
640 640 fp = open(os.path.join(repo.path, 'histedit-state'))
641 641 return pickle.load(fp)
642 642
643 643
644 644 def verifyrules(rules, repo, ctxs):
645 645 """Verify that there exists exactly one edit rule per given changeset.
646 646
647 647 Will abort if there are to many or too few rules, a malformed rule,
648 648 or a rule on a changeset outside of the user-given range.
649 649 """
650 650 parsed = []
651 651 if len(rules) != len(ctxs):
652 652 raise util.Abort(_('must specify a rule for each changeset once'))
653 653 for r in rules:
654 654 if ' ' not in r:
655 655 raise util.Abort(_('malformed line "%s"') % r)
656 656 action, rest = r.split(' ', 1)
657 657 if ' ' in rest.strip():
658 658 ha, rest = rest.split(' ', 1)
659 659 else:
660 660 ha = r.strip()
661 661 try:
662 662 if repo[ha] not in ctxs:
663 663 raise util.Abort(
664 664 _('may not use changesets other than the ones listed'))
665 665 except error.RepoError:
666 666 raise util.Abort(_('unknown changeset %s listed') % ha)
667 667 if action not in actiontable:
668 668 raise util.Abort(_('unknown action "%s"') % action)
669 669 parsed.append([action, ha])
670 670 return parsed
General Comments 0
You need to be logged in to leave comments. Login now