##// END OF EJS Templates
histedit: sync docstring with edit comment...
Wagner Bruna -
r17335:b5c56078 stable
parent child Browse files
Show More
@@ -1,715 +1,715 b''
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 # f, fold = use commit, but fold into previous commit
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 # f, fold = use commit, but fold into previous commit
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 editcomment = _("""# Edit history between %s and %s
167 167 #
168 168 # Commands:
169 169 # p, pick = use commit
170 170 # e, edit = use commit, but stop for amending
171 171 # f, fold = use commit, but fold into previous commit (combines N and N-1)
172 172 # d, drop = remove commit from history
173 173 # m, mess = edit message without changing commit content
174 174 #
175 175 """)
176 176
177 177 def between(repo, old, new, keep):
178 178 revs = [old]
179 179 current = old
180 180 while current != new:
181 181 ctx = repo[current]
182 182 if not keep and len(ctx.children()) > 1:
183 183 raise util.Abort(_('cannot edit history that would orphan nodes'))
184 184 if len(ctx.parents()) != 1 and ctx.parents()[1] != node.nullid:
185 185 raise util.Abort(_("can't edit history with merges"))
186 186 if not ctx.children():
187 187 current = new
188 188 else:
189 189 current = ctx.children()[0].node()
190 190 revs.append(current)
191 191 if len(repo[current].children()) and not keep:
192 192 raise util.Abort(_('cannot edit history that would orphan nodes'))
193 193 return revs
194 194
195 195
196 196 def pick(ui, repo, ctx, ha, opts):
197 197 oldctx = repo[ha]
198 198 if oldctx.parents()[0] == ctx:
199 199 ui.debug('node %s unchanged\n' % ha)
200 200 return oldctx, [], [], []
201 201 hg.update(repo, ctx.node())
202 202 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
203 203 fp = os.fdopen(fd, 'w')
204 204 diffopts = patch.diffopts(ui, opts)
205 205 diffopts.git = True
206 206 diffopts.ignorews = False
207 207 diffopts.ignorewsamount = False
208 208 diffopts.ignoreblanklines = False
209 209 gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
210 210 for chunk in gen:
211 211 fp.write(chunk)
212 212 fp.close()
213 213 try:
214 214 files = set()
215 215 try:
216 216 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
217 217 if not files:
218 218 ui.warn(_('%s: empty changeset')
219 219 % node.hex(ha))
220 220 return ctx, [], [], []
221 221 finally:
222 222 os.unlink(patchfile)
223 223 except Exception:
224 224 raise util.Abort(_('Fix up the change and run '
225 225 'hg histedit --continue'))
226 226 n = repo.commit(text=oldctx.description(), user=oldctx.user(),
227 227 date=oldctx.date(), extra=oldctx.extra())
228 228 return repo[n], [n], [oldctx.node()], []
229 229
230 230
231 231 def edit(ui, repo, ctx, ha, opts):
232 232 oldctx = repo[ha]
233 233 hg.update(repo, ctx.node())
234 234 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
235 235 fp = os.fdopen(fd, 'w')
236 236 diffopts = patch.diffopts(ui, opts)
237 237 diffopts.git = True
238 238 diffopts.ignorews = False
239 239 diffopts.ignorewsamount = False
240 240 diffopts.ignoreblanklines = False
241 241 gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
242 242 for chunk in gen:
243 243 fp.write(chunk)
244 244 fp.close()
245 245 try:
246 246 files = set()
247 247 try:
248 248 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
249 249 finally:
250 250 os.unlink(patchfile)
251 251 except Exception:
252 252 pass
253 253 raise util.Abort(_('Make changes as needed, you may commit or record as '
254 254 'needed now.\nWhen you are finished, run hg'
255 255 ' histedit --continue to resume.'))
256 256
257 257 def fold(ui, repo, ctx, ha, opts):
258 258 oldctx = repo[ha]
259 259 hg.update(repo, ctx.node())
260 260 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
261 261 fp = os.fdopen(fd, 'w')
262 262 diffopts = patch.diffopts(ui, opts)
263 263 diffopts.git = True
264 264 diffopts.ignorews = False
265 265 diffopts.ignorewsamount = False
266 266 diffopts.ignoreblanklines = False
267 267 gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
268 268 for chunk in gen:
269 269 fp.write(chunk)
270 270 fp.close()
271 271 try:
272 272 files = set()
273 273 try:
274 274 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
275 275 if not files:
276 276 ui.warn(_('%s: empty changeset')
277 277 % node.hex(ha))
278 278 return ctx, [], [], []
279 279 finally:
280 280 os.unlink(patchfile)
281 281 except Exception:
282 282 raise util.Abort(_('Fix up the change and run '
283 283 'hg histedit --continue'))
284 284 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
285 285 date=oldctx.date(), extra=oldctx.extra())
286 286 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
287 287
288 288 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
289 289 parent = ctx.parents()[0].node()
290 290 hg.update(repo, parent)
291 291 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
292 292 fp = os.fdopen(fd, 'w')
293 293 diffopts = patch.diffopts(ui, opts)
294 294 diffopts.git = True
295 295 diffopts.ignorews = False
296 296 diffopts.ignorewsamount = False
297 297 diffopts.ignoreblanklines = False
298 298 gen = patch.diff(repo, parent, newnode, opts=diffopts)
299 299 for chunk in gen:
300 300 fp.write(chunk)
301 301 fp.close()
302 302 files = set()
303 303 try:
304 304 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
305 305 finally:
306 306 os.unlink(patchfile)
307 307 newmessage = '\n***\n'.join(
308 308 [ctx.description()] +
309 309 [repo[r].description() for r in internalchanges] +
310 310 [oldctx.description()]) + '\n'
311 311 # If the changesets are from the same author, keep it.
312 312 if ctx.user() == oldctx.user():
313 313 username = ctx.user()
314 314 else:
315 315 username = ui.username()
316 316 newmessage = ui.edit(newmessage, username)
317 317 n = repo.commit(text=newmessage, user=username,
318 318 date=max(ctx.date(), oldctx.date()), extra=oldctx.extra())
319 319 return repo[n], [n], [oldctx.node(), ctx.node()], [newnode]
320 320
321 321 def drop(ui, repo, ctx, ha, opts):
322 322 return ctx, [], [repo[ha].node()], []
323 323
324 324
325 325 def message(ui, repo, ctx, ha, opts):
326 326 oldctx = repo[ha]
327 327 hg.update(repo, ctx.node())
328 328 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
329 329 fp = os.fdopen(fd, 'w')
330 330 diffopts = patch.diffopts(ui, opts)
331 331 diffopts.git = True
332 332 diffopts.ignorews = False
333 333 diffopts.ignorewsamount = False
334 334 diffopts.ignoreblanklines = False
335 335 gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
336 336 for chunk in gen:
337 337 fp.write(chunk)
338 338 fp.close()
339 339 try:
340 340 files = set()
341 341 try:
342 342 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
343 343 finally:
344 344 os.unlink(patchfile)
345 345 except Exception:
346 346 raise util.Abort(_('Fix up the change and run '
347 347 'hg histedit --continue'))
348 348 message = oldctx.description() + '\n'
349 349 message = ui.edit(message, ui.username())
350 350 new = repo.commit(text=message, user=oldctx.user(), date=oldctx.date(),
351 351 extra=oldctx.extra())
352 352 newctx = repo[new]
353 353 if oldctx.node() != newctx.node():
354 354 return newctx, [new], [oldctx.node()], []
355 355 # We didn't make an edit, so just indicate no replaced nodes
356 356 return newctx, [new], [], []
357 357
358 358
359 359 def makedesc(c):
360 360 summary = ''
361 361 if c.description():
362 362 summary = c.description().splitlines()[0]
363 363 line = 'pick %s %d %s' % (c.hex()[:12], c.rev(), summary)
364 364 return line[:80] # trim to 80 chars so it's not stupidly wide in my editor
365 365
366 366 actiontable = {'p': pick,
367 367 'pick': pick,
368 368 'e': edit,
369 369 'edit': edit,
370 370 'f': fold,
371 371 'fold': fold,
372 372 'd': drop,
373 373 'drop': drop,
374 374 'm': message,
375 375 'mess': message,
376 376 }
377 377
378 378 @command('histedit',
379 379 [('', 'commands', '',
380 380 _('Read history edits from the specified file.')),
381 381 ('c', 'continue', False, _('continue an edit already in progress')),
382 382 ('k', 'keep', False,
383 383 _("don't strip old nodes after edit is complete")),
384 384 ('', 'abort', False, _('abort an edit in progress')),
385 385 ('o', 'outgoing', False, _('changesets not found in destination')),
386 386 ('f', 'force', False,
387 387 _('force outgoing even for unrelated repositories')),
388 388 ('r', 'rev', [], _('first revision to be edited'))],
389 389 _("[PARENT]"))
390 390 def histedit(ui, repo, *parent, **opts):
391 391 """interactively edit changeset history
392 392 """
393 393 # TODO only abort if we try and histedit mq patches, not just
394 394 # blanket if mq patches are applied somewhere
395 395 mq = getattr(repo, 'mq', None)
396 396 if mq and mq.applied:
397 397 raise util.Abort(_('source has mq patches applied'))
398 398
399 399 parent = list(parent) + opts.get('rev', [])
400 400 if opts.get('outgoing'):
401 401 if len(parent) > 1:
402 402 raise util.Abort(
403 403 _('only one repo argument allowed with --outgoing'))
404 404 elif parent:
405 405 parent = parent[0]
406 406
407 407 dest = ui.expandpath(parent or 'default-push', parent or 'default')
408 408 dest, revs = hg.parseurl(dest, None)[:2]
409 409 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
410 410
411 411 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
412 412 other = hg.peer(repo, opts, dest)
413 413
414 414 if revs:
415 415 revs = [repo.lookup(rev) for rev in revs]
416 416
417 417 parent = discovery.findcommonoutgoing(
418 418 repo, other, [], force=opts.get('force')).missing[0:1]
419 419 else:
420 420 if opts.get('force'):
421 421 raise util.Abort(_('--force only allowed with --outgoing'))
422 422
423 423 if opts.get('continue', False):
424 424 if len(parent) != 0:
425 425 raise util.Abort(_('no arguments allowed with --continue'))
426 426 (parentctxnode, created, replaced,
427 427 tmpnodes, existing, rules, keep, tip, replacemap) = readstate(repo)
428 428 currentparent, wantnull = repo.dirstate.parents()
429 429 parentctx = repo[parentctxnode]
430 430 # existing is the list of revisions initially considered by
431 431 # histedit. Here we use it to list new changesets, descendants
432 432 # of parentctx without an 'existing' changeset in-between. We
433 433 # also have to exclude 'existing' changesets which were
434 434 # previously dropped.
435 435 descendants = set(c.node() for c in
436 436 repo.set('(%n::) - %n', parentctxnode, parentctxnode))
437 437 existing = set(existing)
438 438 notdropped = set(n for n in existing if n in descendants and
439 439 (n not in replacemap or replacemap[n] in descendants))
440 440 # Discover any nodes the user has added in the interim. We can
441 441 # miss changesets which were dropped and recreated the same.
442 442 newchildren = list(c.node() for c in repo.set(
443 443 'sort(%ln - (%ln or %ln::))', descendants, existing, notdropped))
444 444 action, currentnode = rules.pop(0)
445 445 if action in ('f', 'fold'):
446 446 tmpnodes.extend(newchildren)
447 447 else:
448 448 created.extend(newchildren)
449 449
450 450 m, a, r, d = repo.status()[:4]
451 451 oldctx = repo[currentnode]
452 452 message = oldctx.description() + '\n'
453 453 if action in ('e', 'edit', 'm', 'mess'):
454 454 message = ui.edit(message, ui.username())
455 455 elif action in ('f', 'fold'):
456 456 message = 'fold-temp-revision %s' % currentnode
457 457 new = None
458 458 if m or a or r or d:
459 459 new = repo.commit(text=message, user=oldctx.user(),
460 460 date=oldctx.date(), extra=oldctx.extra())
461 461
462 462 # If we're resuming a fold and we have new changes, mark the
463 463 # replacements and finish the fold. If not, it's more like a
464 464 # drop of the changesets that disappeared, and we can skip
465 465 # this step.
466 466 if action in ('f', 'fold') and (new or newchildren):
467 467 if new:
468 468 tmpnodes.append(new)
469 469 else:
470 470 new = newchildren[-1]
471 471 (parentctx, created_, replaced_, tmpnodes_) = finishfold(
472 472 ui, repo, parentctx, oldctx, new, opts, newchildren)
473 473 replaced.extend(replaced_)
474 474 created.extend(created_)
475 475 tmpnodes.extend(tmpnodes_)
476 476 elif action not in ('d', 'drop'):
477 477 if new != oldctx.node():
478 478 replaced.append(oldctx.node())
479 479 if new:
480 480 if new != oldctx.node():
481 481 created.append(new)
482 482 parentctx = repo[new]
483 483
484 484 elif opts.get('abort', False):
485 485 if len(parent) != 0:
486 486 raise util.Abort(_('no arguments allowed with --abort'))
487 487 (parentctxnode, created, replaced, tmpnodes,
488 488 existing, rules, keep, tip, replacemap) = readstate(repo)
489 489 ui.debug('restore wc to old tip %s\n' % node.hex(tip))
490 490 hg.clean(repo, tip)
491 491 ui.debug('should strip created nodes %s\n' %
492 492 ', '.join([node.hex(n)[:12] for n in created]))
493 493 ui.debug('should strip temp nodes %s\n' %
494 494 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
495 495 for nodes in (created, tmpnodes):
496 496 lock = None
497 497 try:
498 498 lock = repo.lock()
499 499 for n in reversed(nodes):
500 500 try:
501 501 repair.strip(ui, repo, n)
502 502 except error.LookupError:
503 503 pass
504 504 finally:
505 505 lockmod.release(lock)
506 506 os.unlink(os.path.join(repo.path, 'histedit-state'))
507 507 return
508 508 else:
509 509 cmdutil.bailifchanged(repo)
510 510 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
511 511 raise util.Abort(_('history edit already in progress, try '
512 512 '--continue or --abort'))
513 513
514 514 tip, empty = repo.dirstate.parents()
515 515
516 516
517 517 if len(parent) != 1:
518 518 raise util.Abort(_('histedit requires exactly one parent revision'))
519 519 parent = scmutil.revsingle(repo, parent[0]).node()
520 520
521 521 keep = opts.get('keep', False)
522 522 revs = between(repo, parent, tip, keep)
523 523
524 524 ctxs = [repo[r] for r in revs]
525 525 existing = [r.node() for r in ctxs]
526 526 rules = opts.get('commands', '')
527 527 if not rules:
528 528 rules = '\n'.join([makedesc(c) for c in ctxs])
529 529 rules += '\n\n'
530 530 rules += editcomment % (node.hex(parent)[:12], node.hex(tip)[:12])
531 531 rules = ui.edit(rules, ui.username())
532 532 # Save edit rules in .hg/histedit-last-edit.txt in case
533 533 # the user needs to ask for help after something
534 534 # surprising happens.
535 535 f = open(repo.join('histedit-last-edit.txt'), 'w')
536 536 f.write(rules)
537 537 f.close()
538 538 else:
539 539 f = open(rules)
540 540 rules = f.read()
541 541 f.close()
542 542 rules = [l for l in (r.strip() for r in rules.splitlines())
543 543 if l and not l[0] == '#']
544 544 rules = verifyrules(rules, repo, ctxs)
545 545
546 546 parentctx = repo[parent].parents()[0]
547 547 keep = opts.get('keep', False)
548 548 replaced = []
549 549 replacemap = {}
550 550 tmpnodes = []
551 551 created = []
552 552
553 553
554 554 while rules:
555 555 writestate(repo, parentctx.node(), created, replaced,
556 556 tmpnodes, existing, rules, keep, tip, replacemap)
557 557 action, ha = rules.pop(0)
558 558 (parentctx, created_, replaced_, tmpnodes_) = actiontable[action](
559 559 ui, repo, parentctx, ha, opts)
560 560
561 561 if replaced_:
562 562 clen, rlen = len(created_), len(replaced_)
563 563 if clen == rlen == 1:
564 564 ui.debug('histedit: exact replacement of %s with %s\n' % (
565 565 node.short(replaced_[0]), node.short(created_[0])))
566 566
567 567 replacemap[replaced_[0]] = created_[0]
568 568 elif clen > rlen:
569 569 assert rlen == 1, ('unexpected replacement of '
570 570 '%d changes with %d changes' % (rlen, clen))
571 571 # made more changesets than we're replacing
572 572 # TODO synthesize patch names for created patches
573 573 replacemap[replaced_[0]] = created_[-1]
574 574 ui.debug('histedit: created many, assuming %s replaced by %s' %
575 575 (node.short(replaced_[0]), node.short(created_[-1])))
576 576 elif rlen > clen:
577 577 if not created_:
578 578 # This must be a drop. Try and put our metadata on
579 579 # the parent change.
580 580 assert rlen == 1
581 581 r = replaced_[0]
582 582 ui.debug('histedit: %s seems replaced with nothing, '
583 583 'finding a parent\n' % (node.short(r)))
584 584 pctx = repo[r].parents()[0]
585 585 if pctx.node() in replacemap:
586 586 ui.debug('histedit: parent is already replaced\n')
587 587 replacemap[r] = replacemap[pctx.node()]
588 588 else:
589 589 replacemap[r] = pctx.node()
590 590 ui.debug('histedit: %s best replaced by %s\n' % (
591 591 node.short(r), node.short(replacemap[r])))
592 592 else:
593 593 assert len(created_) == 1
594 594 for r in replaced_:
595 595 ui.debug('histedit: %s replaced by %s\n' % (
596 596 node.short(r), node.short(created_[0])))
597 597 replacemap[r] = created_[0]
598 598 else:
599 599 assert False, (
600 600 'Unhandled case in replacement mapping! '
601 601 'replacing %d changes with %d changes' % (rlen, clen))
602 602 created.extend(created_)
603 603 replaced.extend(replaced_)
604 604 tmpnodes.extend(tmpnodes_)
605 605
606 606 hg.update(repo, parentctx.node())
607 607
608 608 if not keep:
609 609 if replacemap:
610 610 ui.note(_('histedit: Should update metadata for the following '
611 611 'changes:\n'))
612 612
613 613 def copybms(old, new):
614 614 if old in tmpnodes or old in created:
615 615 # can't have any metadata we'd want to update
616 616 return
617 617 while new in replacemap:
618 618 new = replacemap[new]
619 619 ui.note(_('histedit: %s to %s\n') % (node.short(old),
620 620 node.short(new)))
621 621 octx = repo[old]
622 622 marks = octx.bookmarks()
623 623 if marks:
624 624 ui.note(_('histedit: moving bookmarks %s\n') %
625 625 ', '.join(marks))
626 626 for mark in marks:
627 627 repo._bookmarks[mark] = new
628 628 bookmarks.write(repo)
629 629
630 630 # We assume that bookmarks on the tip should remain
631 631 # tipmost, but bookmarks on non-tip changesets should go
632 632 # to their most reasonable successor. As a result, find
633 633 # the old tip and new tip and copy those bookmarks first,
634 634 # then do the rest of the bookmark copies.
635 635 oldtip = sorted(replacemap.keys(), key=repo.changelog.rev)[-1]
636 636 newtip = sorted(replacemap.values(), key=repo.changelog.rev)[-1]
637 637 copybms(oldtip, newtip)
638 638
639 639 for old, new in sorted(replacemap.iteritems()):
640 640 copybms(old, new)
641 641 # TODO update mq state
642 642
643 643 ui.debug('should strip replaced nodes %s\n' %
644 644 ', '.join([node.hex(n)[:12] for n in replaced]))
645 645 lock = None
646 646 try:
647 647 lock = repo.lock()
648 648 for n in sorted(replaced, key=lambda x: repo[x].rev()):
649 649 try:
650 650 repair.strip(ui, repo, n)
651 651 except error.LookupError:
652 652 pass
653 653 finally:
654 654 lockmod.release(lock)
655 655
656 656 ui.debug('should strip temp nodes %s\n' %
657 657 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
658 658 lock = None
659 659 try:
660 660 lock = repo.lock()
661 661 for n in reversed(tmpnodes):
662 662 try:
663 663 repair.strip(ui, repo, n)
664 664 except error.LookupError:
665 665 pass
666 666 finally:
667 667 lockmod.release(lock)
668 668 os.unlink(os.path.join(repo.path, 'histedit-state'))
669 669 if os.path.exists(repo.sjoin('undo')):
670 670 os.unlink(repo.sjoin('undo'))
671 671
672 672
673 673 def writestate(repo, parentctxnode, created, replaced,
674 674 tmpnodes, existing, rules, keep, oldtip, replacemap):
675 675 fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
676 676 pickle.dump((parentctxnode, created, replaced,
677 677 tmpnodes, existing, rules, keep, oldtip, replacemap),
678 678 fp)
679 679 fp.close()
680 680
681 681 def readstate(repo):
682 682 """Returns a tuple of (parentnode, created, replaced, tmp, existing, rules,
683 683 keep, oldtip, replacemap ).
684 684 """
685 685 fp = open(os.path.join(repo.path, 'histedit-state'))
686 686 return pickle.load(fp)
687 687
688 688
689 689 def verifyrules(rules, repo, ctxs):
690 690 """Verify that there exists exactly one edit rule per given changeset.
691 691
692 692 Will abort if there are to many or too few rules, a malformed rule,
693 693 or a rule on a changeset outside of the user-given range.
694 694 """
695 695 parsed = []
696 696 if len(rules) != len(ctxs):
697 697 raise util.Abort(_('must specify a rule for each changeset once'))
698 698 for r in rules:
699 699 if ' ' not in r:
700 700 raise util.Abort(_('malformed line "%s"') % r)
701 701 action, rest = r.split(' ', 1)
702 702 if ' ' in rest.strip():
703 703 ha, rest = rest.split(' ', 1)
704 704 else:
705 705 ha = r.strip()
706 706 try:
707 707 if repo[ha] not in ctxs:
708 708 raise util.Abort(
709 709 _('may not use changesets other than the ones listed'))
710 710 except error.RepoError:
711 711 raise util.Abort(_('unknown changeset %s listed') % ha)
712 712 if action not in actiontable:
713 713 raise util.Abort(_('unknown action "%s"') % action)
714 714 parsed.append([action, ha])
715 715 return parsed
General Comments 0
You need to be logged in to leave comments. Login now