##// END OF EJS Templates
merge with stable
Matt Mackall -
r17341:b131e24e merge default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

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