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