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