##// END OF EJS Templates
histedit: convert pick action into a class...
Durham Goode -
r24767:477e7693 default
parent child Browse files
Show More
@@ -1,1222 +1,1208
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 c561b4e977df and 7c2fd3b9020c
32 32 #
33 33 # Commits are listed from least to most recent
34 34 #
35 35 # Commands:
36 36 # p, pick = use commit
37 37 # e, edit = use commit, but stop for amending
38 38 # f, fold = use commit, but combine it with the one above
39 39 # r, roll = like fold, but discard this commit's description
40 40 # d, drop = remove commit from history
41 41 # m, mess = edit message without changing commit content
42 42 #
43 43
44 44 In this file, lines beginning with ``#`` are ignored. You must specify a rule
45 45 for each revision in your history. For example, if you had meant to add gamma
46 46 before beta, and then wanted to add delta in the same revision as beta, you
47 47 would reorganize the file to look like this::
48 48
49 49 pick 030b686bedc4 Add gamma
50 50 pick c561b4e977df Add beta
51 51 fold 7c2fd3b9020c Add delta
52 52
53 53 # Edit history between c561b4e977df and 7c2fd3b9020c
54 54 #
55 55 # Commits are listed from least to most recent
56 56 #
57 57 # Commands:
58 58 # p, pick = use commit
59 59 # e, edit = use commit, but stop for amending
60 60 # f, fold = use commit, but combine it with the one above
61 61 # r, roll = like fold, but discard this commit's description
62 62 # d, drop = remove commit from history
63 63 # m, mess = edit message without changing commit content
64 64 #
65 65
66 66 At which point you close the editor and ``histedit`` starts working. When you
67 67 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
68 68 those revisions together, offering you a chance to clean up the commit message::
69 69
70 70 Add beta
71 71 ***
72 72 Add delta
73 73
74 74 Edit the commit message to your liking, then close the editor. For
75 75 this example, let's assume that the commit message was changed to
76 76 ``Add beta and delta.`` After histedit has run and had a chance to
77 77 remove any old or temporary revisions it needed, the history looks
78 78 like this::
79 79
80 80 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
81 81 | Add beta and delta.
82 82 |
83 83 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
84 84 | Add gamma
85 85 |
86 86 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
87 87 Add alpha
88 88
89 89 Note that ``histedit`` does *not* remove any revisions (even its own temporary
90 90 ones) until after it has completed all the editing operations, so it will
91 91 probably perform several strip operations when it's done. For the above example,
92 92 it had to run strip twice. Strip can be slow depending on a variety of factors,
93 93 so you might need to be a little patient. You can choose to keep the original
94 94 revisions by passing the ``--keep`` flag.
95 95
96 96 The ``edit`` operation will drop you back to a command prompt,
97 97 allowing you to edit files freely, or even use ``hg record`` to commit
98 98 some changes as a separate commit. When you're done, any remaining
99 99 uncommitted changes will be committed as well. When done, run ``hg
100 100 histedit --continue`` to finish this step. You'll be prompted for a
101 101 new commit message, but the default commit message will be the
102 102 original message for the ``edit`` ed revision.
103 103
104 104 The ``message`` operation will give you a chance to revise a commit
105 105 message without changing the contents. It's a shortcut for doing
106 106 ``edit`` immediately followed by `hg histedit --continue``.
107 107
108 108 If ``histedit`` encounters a conflict when moving a revision (while
109 109 handling ``pick`` or ``fold``), it'll stop in a similar manner to
110 110 ``edit`` with the difference that it won't prompt you for a commit
111 111 message when done. If you decide at this point that you don't like how
112 112 much work it will be to rearrange history, or that you made a mistake,
113 113 you can use ``hg histedit --abort`` to abandon the new changes you
114 114 have made and return to the state before you attempted to edit your
115 115 history.
116 116
117 117 If we clone the histedit-ed example repository above and add four more
118 118 changes, such that we have the following history::
119 119
120 120 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
121 121 | Add theta
122 122 |
123 123 o 5 140988835471 2009-04-27 18:04 -0500 stefan
124 124 | Add eta
125 125 |
126 126 o 4 122930637314 2009-04-27 18:04 -0500 stefan
127 127 | Add zeta
128 128 |
129 129 o 3 836302820282 2009-04-27 18:04 -0500 stefan
130 130 | Add epsilon
131 131 |
132 132 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
133 133 | Add beta and delta.
134 134 |
135 135 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
136 136 | Add gamma
137 137 |
138 138 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
139 139 Add alpha
140 140
141 141 If you run ``hg histedit --outgoing`` on the clone then it is the same
142 142 as running ``hg histedit 836302820282``. If you need plan to push to a
143 143 repository that Mercurial does not detect to be related to the source
144 144 repo, you can add a ``--force`` option.
145 145
146 146 Histedit rule lines are truncated to 80 characters by default. You
147 147 can customise this behaviour by setting a different length in your
148 148 configuration file:
149 149
150 150 [histedit]
151 151 linelen = 120 # truncate rule lines at 120 characters
152 152 """
153 153
154 154 try:
155 155 import cPickle as pickle
156 156 pickle.dump # import now
157 157 except ImportError:
158 158 import pickle
159 159 import errno
160 160 import inspect
161 161 import os
162 162 import sys
163 163
164 164 from mercurial import cmdutil
165 165 from mercurial import discovery
166 166 from mercurial import error
167 167 from mercurial import changegroup
168 168 from mercurial import copies
169 169 from mercurial import context
170 170 from mercurial import exchange
171 171 from mercurial import extensions
172 172 from mercurial import hg
173 173 from mercurial import node
174 174 from mercurial import repair
175 175 from mercurial import scmutil
176 176 from mercurial import util
177 177 from mercurial import obsolete
178 178 from mercurial import merge as mergemod
179 179 from mercurial.lock import release
180 180 from mercurial.i18n import _
181 181
182 182 cmdtable = {}
183 183 command = cmdutil.command(cmdtable)
184 184
185 185 testedwith = 'internal'
186 186
187 187 # i18n: command names and abbreviations must remain untranslated
188 188 editcomment = _("""# Edit history between %s and %s
189 189 #
190 190 # Commits are listed from least to most recent
191 191 #
192 192 # Commands:
193 193 # p, pick = use commit
194 194 # e, edit = use commit, but stop for amending
195 195 # f, fold = use commit, but combine it with the one above
196 196 # r, roll = like fold, but discard this commit's description
197 197 # d, drop = remove commit from history
198 198 # m, mess = edit message without changing commit content
199 199 #
200 200 """)
201 201
202 202 class histeditstate(object):
203 203 def __init__(self, repo, parentctxnode=None, rules=None, keep=None,
204 204 topmost=None, replacements=None, lock=None, wlock=None):
205 205 self.repo = repo
206 206 self.rules = rules
207 207 self.keep = keep
208 208 self.topmost = topmost
209 209 self.parentctxnode = parentctxnode
210 210 self.lock = lock
211 211 self.wlock = wlock
212 212 self.backupfile = None
213 213 if replacements is None:
214 214 self.replacements = []
215 215 else:
216 216 self.replacements = replacements
217 217
218 218 def read(self):
219 219 """Load histedit state from disk and set fields appropriately."""
220 220 try:
221 221 fp = self.repo.vfs('histedit-state', 'r')
222 222 except IOError, err:
223 223 if err.errno != errno.ENOENT:
224 224 raise
225 225 raise util.Abort(_('no histedit in progress'))
226 226
227 227 try:
228 228 data = pickle.load(fp)
229 229 parentctxnode, rules, keep, topmost, replacements = data
230 230 backupfile = None
231 231 except pickle.UnpicklingError:
232 232 data = self._load()
233 233 parentctxnode, rules, keep, topmost, replacements, backupfile = data
234 234
235 235 self.parentctxnode = parentctxnode
236 236 self.rules = rules
237 237 self.keep = keep
238 238 self.topmost = topmost
239 239 self.replacements = replacements
240 240 self.backupfile = backupfile
241 241
242 242 def write(self):
243 243 fp = self.repo.vfs('histedit-state', 'w')
244 244 fp.write('v1\n')
245 245 fp.write('%s\n' % node.hex(self.parentctxnode))
246 246 fp.write('%s\n' % node.hex(self.topmost))
247 247 fp.write('%s\n' % self.keep)
248 248 fp.write('%d\n' % len(self.rules))
249 249 for rule in self.rules:
250 250 fp.write('%s%s\n' % (rule[1], rule[0]))
251 251 fp.write('%d\n' % len(self.replacements))
252 252 for replacement in self.replacements:
253 253 fp.write('%s%s\n' % (node.hex(replacement[0]), ''.join(node.hex(r)
254 254 for r in replacement[1])))
255 255 fp.write('%s\n' % self.backupfile)
256 256 fp.close()
257 257
258 258 def _load(self):
259 259 fp = self.repo.vfs('histedit-state', 'r')
260 260 lines = [l[:-1] for l in fp.readlines()]
261 261
262 262 index = 0
263 263 lines[index] # version number
264 264 index += 1
265 265
266 266 parentctxnode = node.bin(lines[index])
267 267 index += 1
268 268
269 269 topmost = node.bin(lines[index])
270 270 index += 1
271 271
272 272 keep = lines[index] == 'True'
273 273 index += 1
274 274
275 275 # Rules
276 276 rules = []
277 277 rulelen = int(lines[index])
278 278 index += 1
279 279 for i in xrange(rulelen):
280 280 rule = lines[index]
281 281 rulehash = rule[:40]
282 282 ruleaction = rule[40:]
283 283 rules.append((ruleaction, rulehash))
284 284 index += 1
285 285
286 286 # Replacements
287 287 replacements = []
288 288 replacementlen = int(lines[index])
289 289 index += 1
290 290 for i in xrange(replacementlen):
291 291 replacement = lines[index]
292 292 original = node.bin(replacement[:40])
293 293 succ = [node.bin(replacement[i:i + 40]) for i in
294 294 range(40, len(replacement), 40)]
295 295 replacements.append((original, succ))
296 296 index += 1
297 297
298 298 backupfile = lines[index]
299 299 index += 1
300 300
301 301 fp.close()
302 302
303 303 return parentctxnode, rules, keep, topmost, replacements, backupfile
304 304
305 305 def clear(self):
306 306 self.repo.vfs.unlink('histedit-state')
307 307
308 308 class histeditaction(object):
309 309 def __init__(self, state, node):
310 310 self.state = state
311 311 self.repo = state.repo
312 312 self.node = node
313 313
314 314 @classmethod
315 315 def fromrule(cls, state, rule):
316 316 """Parses the given rule, returning an instance of the histeditaction.
317 317 """
318 318 repo = state.repo
319 319 rulehash = rule.strip().split(' ', 1)[0]
320 320 try:
321 321 node = repo[rulehash].node()
322 322 except error.RepoError:
323 323 raise util.Abort(_('unknown changeset %s listed') % rulehash[:12])
324 324 return cls(state, node)
325 325
326 326 def run(self):
327 327 """Runs the action. The default behavior is simply apply the action's
328 328 rulectx onto the current parentctx."""
329 329 self.applychange()
330 330 self.continuedirty()
331 331 return self.continueclean()
332 332
333 333 def applychange(self):
334 334 """Applies the changes from this action's rulectx onto the current
335 335 parentctx, but does not commit them."""
336 336 repo = self.repo
337 337 rulectx = repo[self.node]
338 338 hg.update(repo, self.state.parentctxnode)
339 339 stats = applychanges(repo.ui, repo, rulectx, {})
340 340 if stats and stats[3] > 0:
341 341 raise error.InterventionRequired(_('Fix up the change and run '
342 342 'hg histedit --continue'))
343 343
344 344 def continuedirty(self):
345 345 """Continues the action when changes have been applied to the working
346 346 copy. The default behavior is to commit the dirty changes."""
347 347 repo = self.repo
348 348 rulectx = repo[self.node]
349 349
350 350 editor = self.commiteditor()
351 351 commit = commitfuncfor(repo, rulectx)
352 352
353 353 commit(text=rulectx.description(), user=rulectx.user(),
354 354 date=rulectx.date(), extra=rulectx.extra(), editor=editor)
355 355
356 356 def commiteditor(self):
357 357 """The editor to be used to edit the commit message."""
358 358 return False
359 359
360 360 def continueclean(self):
361 361 """Continues the action when the working copy is clean. The default
362 362 behavior is to accept the current commit as the new version of the
363 363 rulectx."""
364 364 ctx = self.repo['.']
365 365 if ctx.node() == self.state.parentctxnode:
366 366 self.repo.ui.warn(_('%s: empty changeset\n') %
367 367 node.short(self.node))
368 368 return ctx, [(self.node, tuple())]
369 369 if ctx.node() == self.node:
370 370 # Nothing changed
371 371 return ctx, []
372 372 return ctx, [(self.node, (ctx.node(),))]
373 373
374 374 def commitfuncfor(repo, src):
375 375 """Build a commit function for the replacement of <src>
376 376
377 377 This function ensure we apply the same treatment to all changesets.
378 378
379 379 - Add a 'histedit_source' entry in extra.
380 380
381 381 Note that fold have its own separated logic because its handling is a bit
382 382 different and not easily factored out of the fold method.
383 383 """
384 384 phasemin = src.phase()
385 385 def commitfunc(**kwargs):
386 386 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
387 387 try:
388 388 repo.ui.setconfig('phases', 'new-commit', phasemin,
389 389 'histedit')
390 390 extra = kwargs.get('extra', {}).copy()
391 391 extra['histedit_source'] = src.hex()
392 392 kwargs['extra'] = extra
393 393 return repo.commit(**kwargs)
394 394 finally:
395 395 repo.ui.restoreconfig(phasebackup)
396 396 return commitfunc
397 397
398 398 def applychanges(ui, repo, ctx, opts):
399 399 """Merge changeset from ctx (only) in the current working directory"""
400 400 wcpar = repo.dirstate.parents()[0]
401 401 if ctx.p1().node() == wcpar:
402 402 # edition ar "in place" we do not need to make any merge,
403 403 # just applies changes on parent for edition
404 404 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
405 405 stats = None
406 406 else:
407 407 try:
408 408 # ui.forcemerge is an internal variable, do not document
409 409 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
410 410 'histedit')
411 411 stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'histedit'])
412 412 finally:
413 413 repo.ui.setconfig('ui', 'forcemerge', '', 'histedit')
414 414 return stats
415 415
416 416 def collapse(repo, first, last, commitopts):
417 417 """collapse the set of revisions from first to last as new one.
418 418
419 419 Expected commit options are:
420 420 - message
421 421 - date
422 422 - username
423 423 Commit message is edited in all cases.
424 424
425 425 This function works in memory."""
426 426 ctxs = list(repo.set('%d::%d', first, last))
427 427 if not ctxs:
428 428 return None
429 429 base = first.parents()[0]
430 430
431 431 # commit a new version of the old changeset, including the update
432 432 # collect all files which might be affected
433 433 files = set()
434 434 for ctx in ctxs:
435 435 files.update(ctx.files())
436 436
437 437 # Recompute copies (avoid recording a -> b -> a)
438 438 copied = copies.pathcopies(base, last)
439 439
440 440 # prune files which were reverted by the updates
441 441 def samefile(f):
442 442 if f in last.manifest():
443 443 a = last.filectx(f)
444 444 if f in base.manifest():
445 445 b = base.filectx(f)
446 446 return (a.data() == b.data()
447 447 and a.flags() == b.flags())
448 448 else:
449 449 return False
450 450 else:
451 451 return f not in base.manifest()
452 452 files = [f for f in files if not samefile(f)]
453 453 # commit version of these files as defined by head
454 454 headmf = last.manifest()
455 455 def filectxfn(repo, ctx, path):
456 456 if path in headmf:
457 457 fctx = last[path]
458 458 flags = fctx.flags()
459 459 mctx = context.memfilectx(repo,
460 460 fctx.path(), fctx.data(),
461 461 islink='l' in flags,
462 462 isexec='x' in flags,
463 463 copied=copied.get(path))
464 464 return mctx
465 465 return None
466 466
467 467 if commitopts.get('message'):
468 468 message = commitopts['message']
469 469 else:
470 470 message = first.description()
471 471 user = commitopts.get('user')
472 472 date = commitopts.get('date')
473 473 extra = commitopts.get('extra')
474 474
475 475 parents = (first.p1().node(), first.p2().node())
476 476 editor = None
477 477 if not commitopts.get('rollup'):
478 478 editor = cmdutil.getcommiteditor(edit=True, editform='histedit.fold')
479 479 new = context.memctx(repo,
480 480 parents=parents,
481 481 text=message,
482 482 files=files,
483 483 filectxfn=filectxfn,
484 484 user=user,
485 485 date=date,
486 486 extra=extra,
487 487 editor=editor)
488 488 return repo.commitctx(new)
489 489
490 def pick(ui, state, ha, opts):
491 repo, ctxnode = state.repo, state.parentctxnode
492 ctx = repo[ctxnode]
493 oldctx = repo[ha]
494 if oldctx.parents()[0] == ctx:
495 ui.debug('node %s unchanged\n' % ha[:12])
496 return oldctx, []
497 hg.update(repo, ctx.node())
498 stats = applychanges(ui, repo, oldctx, opts)
499 if stats and stats[3] > 0:
500 raise error.InterventionRequired(_('Fix up the change and run '
501 'hg histedit --continue'))
502 # drop the second merge parent
503 commit = commitfuncfor(repo, oldctx)
504 n = commit(text=oldctx.description(), user=oldctx.user(),
505 date=oldctx.date(), extra=oldctx.extra())
506 if n is None:
507 ui.warn(_('%s: empty changeset\n') % ha[:12])
508 return ctx, []
509 new = repo[n]
510 return new, [(oldctx.node(), (n,))]
490 class pick(histeditaction):
491 def run(self):
492 rulectx = self.repo[self.node]
493 if rulectx.parents()[0].node() == self.state.parentctxnode:
494 self.repo.ui.debug('node %s unchanged\n' % node.short(self.node))
495 return rulectx, []
511 496
497 return super(pick, self).run()
512 498
513 499 def edit(ui, state, ha, opts):
514 500 repo, ctxnode = state.repo, state.parentctxnode
515 501 ctx = repo[ctxnode]
516 502 oldctx = repo[ha]
517 503 hg.update(repo, ctx.node())
518 504 applychanges(ui, repo, oldctx, opts)
519 505 raise error.InterventionRequired(
520 506 _('Make changes as needed, you may commit or record as needed now.\n'
521 507 'When you are finished, run hg histedit --continue to resume.'))
522 508
523 509 def rollup(ui, state, ha, opts):
524 510 rollupopts = opts.copy()
525 511 rollupopts['rollup'] = True
526 512 return fold(ui, state, ha, rollupopts)
527 513
528 514 def fold(ui, state, ha, opts):
529 515 repo, ctxnode = state.repo, state.parentctxnode
530 516 ctx = repo[ctxnode]
531 517 oldctx = repo[ha]
532 518 hg.update(repo, ctx.node())
533 519 stats = applychanges(ui, repo, oldctx, opts)
534 520 if stats and stats[3] > 0:
535 521 raise error.InterventionRequired(
536 522 _('Fix up the change and run hg histedit --continue'))
537 523 n = repo.commit(text='fold-temp-revision %s' % ha[:12], user=oldctx.user(),
538 524 date=oldctx.date(), extra=oldctx.extra())
539 525 if n is None:
540 526 ui.warn(_('%s: empty changeset') % ha[:12])
541 527 return ctx, []
542 528 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
543 529
544 530 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
545 531 parent = ctx.parents()[0].node()
546 532 hg.update(repo, parent)
547 533 ### prepare new commit data
548 534 commitopts = opts.copy()
549 535 commitopts['user'] = ctx.user()
550 536 # commit message
551 537 if opts.get('rollup'):
552 538 newmessage = ctx.description()
553 539 else:
554 540 newmessage = '\n***\n'.join(
555 541 [ctx.description()] +
556 542 [repo[r].description() for r in internalchanges] +
557 543 [oldctx.description()]) + '\n'
558 544 commitopts['message'] = newmessage
559 545 # date
560 546 commitopts['date'] = max(ctx.date(), oldctx.date())
561 547 extra = ctx.extra().copy()
562 548 # histedit_source
563 549 # note: ctx is likely a temporary commit but that the best we can do here
564 550 # This is sufficient to solve issue3681 anyway
565 551 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex())
566 552 commitopts['extra'] = extra
567 553 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
568 554 try:
569 555 phasemin = max(ctx.phase(), oldctx.phase())
570 556 repo.ui.setconfig('phases', 'new-commit', phasemin, 'histedit')
571 557 n = collapse(repo, ctx, repo[newnode], commitopts)
572 558 finally:
573 559 repo.ui.restoreconfig(phasebackup)
574 560 if n is None:
575 561 return ctx, []
576 562 hg.update(repo, n)
577 563 replacements = [(oldctx.node(), (newnode,)),
578 564 (ctx.node(), (n,)),
579 565 (newnode, (n,)),
580 566 ]
581 567 for ich in internalchanges:
582 568 replacements.append((ich, (n,)))
583 569 return repo[n], replacements
584 570
585 571 def drop(ui, state, ha, opts):
586 572 repo, ctxnode = state.repo, state.parentctxnode
587 573 ctx = repo[ctxnode]
588 574 return ctx, [(repo[ha].node(), ())]
589 575
590 576
591 577 def message(ui, state, ha, opts):
592 578 repo, ctxnode = state.repo, state.parentctxnode
593 579 ctx = repo[ctxnode]
594 580 oldctx = repo[ha]
595 581 hg.update(repo, ctx.node())
596 582 stats = applychanges(ui, repo, oldctx, opts)
597 583 if stats and stats[3] > 0:
598 584 raise error.InterventionRequired(
599 585 _('Fix up the change and run hg histedit --continue'))
600 586 message = oldctx.description()
601 587 commit = commitfuncfor(repo, oldctx)
602 588 editor = cmdutil.getcommiteditor(edit=True, editform='histedit.mess')
603 589 new = commit(text=message, user=oldctx.user(), date=oldctx.date(),
604 590 extra=oldctx.extra(), editor=editor)
605 591 newctx = repo[new]
606 592 if oldctx.node() != newctx.node():
607 593 return newctx, [(oldctx.node(), (new,))]
608 594 # We didn't make an edit, so just indicate no replaced nodes
609 595 return newctx, []
610 596
611 597 def findoutgoing(ui, repo, remote=None, force=False, opts={}):
612 598 """utility function to find the first outgoing changeset
613 599
614 600 Used by initialisation code"""
615 601 dest = ui.expandpath(remote or 'default-push', remote or 'default')
616 602 dest, revs = hg.parseurl(dest, None)[:2]
617 603 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
618 604
619 605 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
620 606 other = hg.peer(repo, opts, dest)
621 607
622 608 if revs:
623 609 revs = [repo.lookup(rev) for rev in revs]
624 610
625 611 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
626 612 if not outgoing.missing:
627 613 raise util.Abort(_('no outgoing ancestors'))
628 614 roots = list(repo.revs("roots(%ln)", outgoing.missing))
629 615 if 1 < len(roots):
630 616 msg = _('there are ambiguous outgoing revisions')
631 617 hint = _('see "hg help histedit" for more detail')
632 618 raise util.Abort(msg, hint=hint)
633 619 return repo.lookup(roots[0])
634 620
635 621 actiontable = {'p': pick,
636 622 'pick': pick,
637 623 'e': edit,
638 624 'edit': edit,
639 625 'f': fold,
640 626 'fold': fold,
641 627 'r': rollup,
642 628 'roll': rollup,
643 629 'd': drop,
644 630 'drop': drop,
645 631 'm': message,
646 632 'mess': message,
647 633 }
648 634
649 635 @command('histedit',
650 636 [('', 'commands', '',
651 637 _('read history edits from the specified file'), _('FILE')),
652 638 ('c', 'continue', False, _('continue an edit already in progress')),
653 639 ('', 'edit-plan', False, _('edit remaining actions list')),
654 640 ('k', 'keep', False,
655 641 _("don't strip old nodes after edit is complete")),
656 642 ('', 'abort', False, _('abort an edit in progress')),
657 643 ('o', 'outgoing', False, _('changesets not found in destination')),
658 644 ('f', 'force', False,
659 645 _('force outgoing even for unrelated repositories')),
660 646 ('r', 'rev', [], _('first revision to be edited'), _('REV'))],
661 647 _("ANCESTOR | --outgoing [URL]"))
662 648 def histedit(ui, repo, *freeargs, **opts):
663 649 """interactively edit changeset history
664 650
665 651 This command edits changesets between ANCESTOR and the parent of
666 652 the working directory.
667 653
668 654 With --outgoing, this edits changesets not found in the
669 655 destination repository. If URL of the destination is omitted, the
670 656 'default-push' (or 'default') path will be used.
671 657
672 658 For safety, this command is aborted, also if there are ambiguous
673 659 outgoing revisions which may confuse users: for example, there are
674 660 multiple branches containing outgoing revisions.
675 661
676 662 Use "min(outgoing() and ::.)" or similar revset specification
677 663 instead of --outgoing to specify edit target revision exactly in
678 664 such ambiguous situation. See :hg:`help revsets` for detail about
679 665 selecting revisions.
680 666
681 667 Returns 0 on success, 1 if user intervention is required (not only
682 668 for intentional "edit" command, but also for resolving unexpected
683 669 conflicts).
684 670 """
685 671 state = histeditstate(repo)
686 672 try:
687 673 state.wlock = repo.wlock()
688 674 state.lock = repo.lock()
689 675 _histedit(ui, repo, state, *freeargs, **opts)
690 676 finally:
691 677 release(state.lock, state.wlock)
692 678
693 679 def _histedit(ui, repo, state, *freeargs, **opts):
694 680 # TODO only abort if we try and histedit mq patches, not just
695 681 # blanket if mq patches are applied somewhere
696 682 mq = getattr(repo, 'mq', None)
697 683 if mq and mq.applied:
698 684 raise util.Abort(_('source has mq patches applied'))
699 685
700 686 # basic argument incompatibility processing
701 687 outg = opts.get('outgoing')
702 688 cont = opts.get('continue')
703 689 editplan = opts.get('edit_plan')
704 690 abort = opts.get('abort')
705 691 force = opts.get('force')
706 692 rules = opts.get('commands', '')
707 693 revs = opts.get('rev', [])
708 694 goal = 'new' # This invocation goal, in new, continue, abort
709 695 if force and not outg:
710 696 raise util.Abort(_('--force only allowed with --outgoing'))
711 697 if cont:
712 698 if util.any((outg, abort, revs, freeargs, rules, editplan)):
713 699 raise util.Abort(_('no arguments allowed with --continue'))
714 700 goal = 'continue'
715 701 elif abort:
716 702 if util.any((outg, revs, freeargs, rules, editplan)):
717 703 raise util.Abort(_('no arguments allowed with --abort'))
718 704 goal = 'abort'
719 705 elif editplan:
720 706 if util.any((outg, revs, freeargs)):
721 707 raise util.Abort(_('only --commands argument allowed with'
722 708 '--edit-plan'))
723 709 goal = 'edit-plan'
724 710 else:
725 711 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
726 712 raise util.Abort(_('history edit already in progress, try '
727 713 '--continue or --abort'))
728 714 if outg:
729 715 if revs:
730 716 raise util.Abort(_('no revisions allowed with --outgoing'))
731 717 if len(freeargs) > 1:
732 718 raise util.Abort(
733 719 _('only one repo argument allowed with --outgoing'))
734 720 else:
735 721 revs.extend(freeargs)
736 722 if len(revs) == 0:
737 723 histeditdefault = ui.config('histedit', 'defaultrev')
738 724 if histeditdefault:
739 725 revs.append(histeditdefault)
740 726 if len(revs) != 1:
741 727 raise util.Abort(
742 728 _('histedit requires exactly one ancestor revision'))
743 729
744 730
745 731 replacements = []
746 732 keep = opts.get('keep', False)
747 733
748 734 # rebuild state
749 735 if goal == 'continue':
750 736 state.read()
751 737 state = bootstrapcontinue(ui, state, opts)
752 738 elif goal == 'edit-plan':
753 739 state.read()
754 740 if not rules:
755 741 comment = editcomment % (state.parentctx, node.short(state.topmost))
756 742 rules = ruleeditor(repo, ui, state.rules, comment)
757 743 else:
758 744 if rules == '-':
759 745 f = sys.stdin
760 746 else:
761 747 f = open(rules)
762 748 rules = f.read()
763 749 f.close()
764 750 rules = [l for l in (r.strip() for r in rules.splitlines())
765 751 if l and not l.startswith('#')]
766 752 rules = verifyrules(rules, repo, [repo[c] for [_a, c] in state.rules])
767 753 state.rules = rules
768 754 state.write()
769 755 return
770 756 elif goal == 'abort':
771 757 state.read()
772 758 mapping, tmpnodes, leafs, _ntm = processreplacement(state)
773 759 ui.debug('restore wc to old parent %s\n' % node.short(state.topmost))
774 760
775 761 # Recover our old commits if necessary
776 762 if not state.topmost in repo and state.backupfile:
777 763 backupfile = repo.join(state.backupfile)
778 764 f = hg.openpath(ui, backupfile)
779 765 gen = exchange.readbundle(ui, f, backupfile)
780 766 changegroup.addchangegroup(repo, gen, 'histedit',
781 767 'bundle:' + backupfile)
782 768 os.remove(backupfile)
783 769
784 770 # check whether we should update away
785 771 parentnodes = [c.node() for c in repo[None].parents()]
786 772 for n in leafs | set([state.parentctxnode]):
787 773 if n in parentnodes:
788 774 hg.clean(repo, state.topmost)
789 775 break
790 776 else:
791 777 pass
792 778 cleanupnode(ui, repo, 'created', tmpnodes)
793 779 cleanupnode(ui, repo, 'temp', leafs)
794 780 state.clear()
795 781 return
796 782 else:
797 783 cmdutil.checkunfinished(repo)
798 784 cmdutil.bailifchanged(repo)
799 785
800 786 topmost, empty = repo.dirstate.parents()
801 787 if outg:
802 788 if freeargs:
803 789 remote = freeargs[0]
804 790 else:
805 791 remote = None
806 792 root = findoutgoing(ui, repo, remote, force, opts)
807 793 else:
808 794 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs)))
809 795 if len(rr) != 1:
810 796 raise util.Abort(_('The specified revisions must have '
811 797 'exactly one common root'))
812 798 root = rr[0].node()
813 799
814 800 revs = between(repo, root, topmost, keep)
815 801 if not revs:
816 802 raise util.Abort(_('%s is not an ancestor of working directory') %
817 803 node.short(root))
818 804
819 805 ctxs = [repo[r] for r in revs]
820 806 if not rules:
821 807 comment = editcomment % (node.short(root), node.short(topmost))
822 808 rules = ruleeditor(repo, ui, [['pick', c] for c in ctxs], comment)
823 809 else:
824 810 if rules == '-':
825 811 f = sys.stdin
826 812 else:
827 813 f = open(rules)
828 814 rules = f.read()
829 815 f.close()
830 816 rules = [l for l in (r.strip() for r in rules.splitlines())
831 817 if l and not l.startswith('#')]
832 818 rules = verifyrules(rules, repo, ctxs)
833 819
834 820 parentctxnode = repo[root].parents()[0].node()
835 821
836 822 state.parentctxnode = parentctxnode
837 823 state.rules = rules
838 824 state.keep = keep
839 825 state.topmost = topmost
840 826 state.replacements = replacements
841 827
842 828 # Create a backup so we can always abort completely.
843 829 backupfile = None
844 830 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
845 831 backupfile = repair._bundle(repo, [parentctxnode], [topmost], root,
846 832 'histedit')
847 833 state.backupfile = backupfile
848 834
849 835 while state.rules:
850 836 state.write()
851 837 action, ha = state.rules.pop(0)
852 838 ui.debug('histedit: processing %s %s\n' % (action, ha[:12]))
853 839 act = actiontable[action]
854 840 if inspect.isclass(act):
855 841 actobj = act.fromrule(state, ha)
856 842 parentctx, replacement_ = actobj.run()
857 843 else:
858 844 parentctx, replacement_ = act(ui, state, ha, opts)
859 845 state.parentctxnode = parentctx.node()
860 846 state.replacements.extend(replacement_)
861 847 state.write()
862 848
863 849 hg.update(repo, state.parentctxnode)
864 850
865 851 mapping, tmpnodes, created, ntm = processreplacement(state)
866 852 if mapping:
867 853 for prec, succs in mapping.iteritems():
868 854 if not succs:
869 855 ui.debug('histedit: %s is dropped\n' % node.short(prec))
870 856 else:
871 857 ui.debug('histedit: %s is replaced by %s\n' % (
872 858 node.short(prec), node.short(succs[0])))
873 859 if len(succs) > 1:
874 860 m = 'histedit: %s'
875 861 for n in succs[1:]:
876 862 ui.debug(m % node.short(n))
877 863
878 864 if not keep:
879 865 if mapping:
880 866 movebookmarks(ui, repo, mapping, state.topmost, ntm)
881 867 # TODO update mq state
882 868 if obsolete.isenabled(repo, obsolete.createmarkersopt):
883 869 markers = []
884 870 # sort by revision number because it sound "right"
885 871 for prec in sorted(mapping, key=repo.changelog.rev):
886 872 succs = mapping[prec]
887 873 markers.append((repo[prec],
888 874 tuple(repo[s] for s in succs)))
889 875 if markers:
890 876 obsolete.createmarkers(repo, markers)
891 877 else:
892 878 cleanupnode(ui, repo, 'replaced', mapping)
893 879
894 880 cleanupnode(ui, repo, 'temp', tmpnodes)
895 881 state.clear()
896 882 if os.path.exists(repo.sjoin('undo')):
897 883 os.unlink(repo.sjoin('undo'))
898 884
899 885 def gatherchildren(repo, ctx):
900 886 # is there any new commit between the expected parent and "."
901 887 #
902 888 # note: does not take non linear new change in account (but previous
903 889 # implementation didn't used them anyway (issue3655)
904 890 newchildren = [c.node() for c in repo.set('(%d::.)', ctx)]
905 891 if ctx.node() != node.nullid:
906 892 if not newchildren:
907 893 return []
908 894 newchildren.pop(0) # remove ctx
909 895 return newchildren
910 896
911 897 def bootstrapcontinue(ui, state, opts):
912 898 repo, parentctxnode = state.repo, state.parentctxnode
913 899 action, currentnode = state.rules.pop(0)
914 900
915 901 s = repo.status()
916 902 replacements = []
917 903
918 904 act = actiontable[action]
919 905 if inspect.isclass(act):
920 906 actobj = act.fromrule(state, currentnode)
921 907 if s.modified or s.added or s.removed or s.deleted:
922 908 actobj.continuedirty()
923 909 s = repo.status()
924 910 if s.modified or s.added or s.removed or s.deleted:
925 911 raise util.Abort(_("working copy still dirty"))
926 912
927 913 parentctx, replacements_ = actobj.continueclean()
928 914 replacements.extend(replacements_)
929 915 else:
930 916 parentctx = repo[parentctxnode]
931 917 ctx = repo[currentnode]
932 918 newchildren = gatherchildren(repo, parentctx)
933 919 # Commit dirty working directory if necessary
934 920 new = None
935 921 if s.modified or s.added or s.removed or s.deleted:
936 922 # prepare the message for the commit to comes
937 923 if action in ('f', 'fold', 'r', 'roll'):
938 924 message = 'fold-temp-revision %s' % currentnode[:12]
939 925 else:
940 926 message = ctx.description()
941 927 editopt = action in ('e', 'edit', 'm', 'mess')
942 canonaction = {'e': 'edit', 'm': 'mess', 'p': 'pick'}
928 canonaction = {'e': 'edit', 'm': 'mess'}
943 929 editform = 'histedit.%s' % canonaction.get(action, action)
944 930 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
945 931 commit = commitfuncfor(repo, ctx)
946 932 new = commit(text=message, user=ctx.user(), date=ctx.date(),
947 933 extra=ctx.extra(), editor=editor)
948 934 if new is not None:
949 935 newchildren.append(new)
950 936
951 937 # track replacements
952 938 if ctx.node() not in newchildren:
953 939 # note: new children may be empty when the changeset is dropped.
954 940 # this happen e.g during conflicting pick where we revert content
955 941 # to parent.
956 942 replacements.append((ctx.node(), tuple(newchildren)))
957 943
958 944 if action in ('f', 'fold', 'r', 'roll'):
959 945 if newchildren:
960 946 # finalize fold operation if applicable
961 947 if new is None:
962 948 new = newchildren[-1]
963 949 else:
964 950 newchildren.pop() # remove new from internal changes
965 951 foldopts = opts
966 952 if action in ('r', 'roll'):
967 953 foldopts = foldopts.copy()
968 954 foldopts['rollup'] = True
969 955 parentctx, repl = finishfold(ui, repo, parentctx, ctx, new,
970 956 foldopts, newchildren)
971 957 replacements.extend(repl)
972 958 else:
973 959 # newchildren is empty if the fold did not result in any commit
974 960 # this happen when all folded change are discarded during the
975 961 # merge.
976 962 replacements.append((ctx.node(), (parentctx.node(),)))
977 963 elif newchildren:
978 964 # otherwise update "parentctx" before proceeding further
979 965 parentctx = repo[newchildren[-1]]
980 966
981 967 state.parentctxnode = parentctx.node()
982 968 state.replacements.extend(replacements)
983 969
984 970 return state
985 971
986 972 def between(repo, old, new, keep):
987 973 """select and validate the set of revision to edit
988 974
989 975 When keep is false, the specified set can't have children."""
990 976 ctxs = list(repo.set('%n::%n', old, new))
991 977 if ctxs and not keep:
992 978 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
993 979 repo.revs('(%ld::) - (%ld)', ctxs, ctxs)):
994 980 raise util.Abort(_('cannot edit history that would orphan nodes'))
995 981 if repo.revs('(%ld) and merge()', ctxs):
996 982 raise util.Abort(_('cannot edit history that contains merges'))
997 983 root = ctxs[0] # list is already sorted by repo.set
998 984 if not root.mutable():
999 985 raise util.Abort(_('cannot edit immutable changeset: %s') % root)
1000 986 return [c.node() for c in ctxs]
1001 987
1002 988 def makedesc(repo, action, rev):
1003 989 """build a initial action line for a ctx
1004 990
1005 991 line are in the form:
1006 992
1007 993 <action> <hash> <rev> <summary>
1008 994 """
1009 995 ctx = repo[rev]
1010 996 summary = ''
1011 997 if ctx.description():
1012 998 summary = ctx.description().splitlines()[0]
1013 999 line = '%s %s %d %s' % (action, ctx, ctx.rev(), summary)
1014 1000 # trim to 80 columns so it's not stupidly wide in my editor
1015 1001 maxlen = repo.ui.configint('histedit', 'linelen', default=80)
1016 1002 maxlen = max(maxlen, 22) # avoid truncating hash
1017 1003 return util.ellipsis(line, maxlen)
1018 1004
1019 1005 def ruleeditor(repo, ui, rules, editcomment=""):
1020 1006 """open an editor to edit rules
1021 1007
1022 1008 rules are in the format [ [act, ctx], ...] like in state.rules
1023 1009 """
1024 1010 rules = '\n'.join([makedesc(repo, act, rev) for [act, rev] in rules])
1025 1011 rules += '\n\n'
1026 1012 rules += editcomment
1027 1013 rules = ui.edit(rules, ui.username())
1028 1014
1029 1015 # Save edit rules in .hg/histedit-last-edit.txt in case
1030 1016 # the user needs to ask for help after something
1031 1017 # surprising happens.
1032 1018 f = open(repo.join('histedit-last-edit.txt'), 'w')
1033 1019 f.write(rules)
1034 1020 f.close()
1035 1021
1036 1022 return rules
1037 1023
1038 1024 def verifyrules(rules, repo, ctxs):
1039 1025 """Verify that there exists exactly one edit rule per given changeset.
1040 1026
1041 1027 Will abort if there are to many or too few rules, a malformed rule,
1042 1028 or a rule on a changeset outside of the user-given range.
1043 1029 """
1044 1030 parsed = []
1045 1031 expected = set(c.hex() for c in ctxs)
1046 1032 seen = set()
1047 1033 for r in rules:
1048 1034 if ' ' not in r:
1049 1035 raise util.Abort(_('malformed line "%s"') % r)
1050 1036 action, rest = r.split(' ', 1)
1051 1037 ha = rest.strip().split(' ', 1)[0]
1052 1038 try:
1053 1039 ha = repo[ha].hex()
1054 1040 except error.RepoError:
1055 1041 raise util.Abort(_('unknown changeset %s listed') % ha[:12])
1056 1042 if ha not in expected:
1057 1043 raise util.Abort(
1058 1044 _('may not use changesets other than the ones listed'))
1059 1045 if ha in seen:
1060 1046 raise util.Abort(_('duplicated command for changeset %s') %
1061 1047 ha[:12])
1062 1048 seen.add(ha)
1063 1049 if action not in actiontable:
1064 1050 raise util.Abort(_('unknown action "%s"') % action)
1065 1051 parsed.append([action, ha])
1066 1052 missing = sorted(expected - seen) # sort to stabilize output
1067 1053 if missing:
1068 1054 raise util.Abort(_('missing rules for changeset %s') %
1069 1055 missing[0][:12],
1070 1056 hint=_('do you want to use the drop action?'))
1071 1057 return parsed
1072 1058
1073 1059 def processreplacement(state):
1074 1060 """process the list of replacements to return
1075 1061
1076 1062 1) the final mapping between original and created nodes
1077 1063 2) the list of temporary node created by histedit
1078 1064 3) the list of new commit created by histedit"""
1079 1065 replacements = state.replacements
1080 1066 allsuccs = set()
1081 1067 replaced = set()
1082 1068 fullmapping = {}
1083 1069 # initialise basic set
1084 1070 # fullmapping record all operation recorded in replacement
1085 1071 for rep in replacements:
1086 1072 allsuccs.update(rep[1])
1087 1073 replaced.add(rep[0])
1088 1074 fullmapping.setdefault(rep[0], set()).update(rep[1])
1089 1075 new = allsuccs - replaced
1090 1076 tmpnodes = allsuccs & replaced
1091 1077 # Reduce content fullmapping into direct relation between original nodes
1092 1078 # and final node created during history edition
1093 1079 # Dropped changeset are replaced by an empty list
1094 1080 toproceed = set(fullmapping)
1095 1081 final = {}
1096 1082 while toproceed:
1097 1083 for x in list(toproceed):
1098 1084 succs = fullmapping[x]
1099 1085 for s in list(succs):
1100 1086 if s in toproceed:
1101 1087 # non final node with unknown closure
1102 1088 # We can't process this now
1103 1089 break
1104 1090 elif s in final:
1105 1091 # non final node, replace with closure
1106 1092 succs.remove(s)
1107 1093 succs.update(final[s])
1108 1094 else:
1109 1095 final[x] = succs
1110 1096 toproceed.remove(x)
1111 1097 # remove tmpnodes from final mapping
1112 1098 for n in tmpnodes:
1113 1099 del final[n]
1114 1100 # we expect all changes involved in final to exist in the repo
1115 1101 # turn `final` into list (topologically sorted)
1116 1102 nm = state.repo.changelog.nodemap
1117 1103 for prec, succs in final.items():
1118 1104 final[prec] = sorted(succs, key=nm.get)
1119 1105
1120 1106 # computed topmost element (necessary for bookmark)
1121 1107 if new:
1122 1108 newtopmost = sorted(new, key=state.repo.changelog.rev)[-1]
1123 1109 elif not final:
1124 1110 # Nothing rewritten at all. we won't need `newtopmost`
1125 1111 # It is the same as `oldtopmost` and `processreplacement` know it
1126 1112 newtopmost = None
1127 1113 else:
1128 1114 # every body died. The newtopmost is the parent of the root.
1129 1115 r = state.repo.changelog.rev
1130 1116 newtopmost = state.repo[sorted(final, key=r)[0]].p1().node()
1131 1117
1132 1118 return final, tmpnodes, new, newtopmost
1133 1119
1134 1120 def movebookmarks(ui, repo, mapping, oldtopmost, newtopmost):
1135 1121 """Move bookmark from old to newly created node"""
1136 1122 if not mapping:
1137 1123 # if nothing got rewritten there is not purpose for this function
1138 1124 return
1139 1125 moves = []
1140 1126 for bk, old in sorted(repo._bookmarks.iteritems()):
1141 1127 if old == oldtopmost:
1142 1128 # special case ensure bookmark stay on tip.
1143 1129 #
1144 1130 # This is arguably a feature and we may only want that for the
1145 1131 # active bookmark. But the behavior is kept compatible with the old
1146 1132 # version for now.
1147 1133 moves.append((bk, newtopmost))
1148 1134 continue
1149 1135 base = old
1150 1136 new = mapping.get(base, None)
1151 1137 if new is None:
1152 1138 continue
1153 1139 while not new:
1154 1140 # base is killed, trying with parent
1155 1141 base = repo[base].p1().node()
1156 1142 new = mapping.get(base, (base,))
1157 1143 # nothing to move
1158 1144 moves.append((bk, new[-1]))
1159 1145 if moves:
1160 1146 marks = repo._bookmarks
1161 1147 for mark, new in moves:
1162 1148 old = marks[mark]
1163 1149 ui.note(_('histedit: moving bookmarks %s from %s to %s\n')
1164 1150 % (mark, node.short(old), node.short(new)))
1165 1151 marks[mark] = new
1166 1152 marks.write()
1167 1153
1168 1154 def cleanupnode(ui, repo, name, nodes):
1169 1155 """strip a group of nodes from the repository
1170 1156
1171 1157 The set of node to strip may contains unknown nodes."""
1172 1158 ui.debug('should strip %s nodes %s\n' %
1173 1159 (name, ', '.join([node.short(n) for n in nodes])))
1174 1160 lock = None
1175 1161 try:
1176 1162 lock = repo.lock()
1177 1163 # Find all node that need to be stripped
1178 1164 # (we hg %lr instead of %ln to silently ignore unknown item
1179 1165 nm = repo.changelog.nodemap
1180 1166 nodes = sorted(n for n in nodes if n in nm)
1181 1167 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
1182 1168 for c in roots:
1183 1169 # We should process node in reverse order to strip tip most first.
1184 1170 # but this trigger a bug in changegroup hook.
1185 1171 # This would reduce bundle overhead
1186 1172 repair.strip(ui, repo, c)
1187 1173 finally:
1188 1174 release(lock)
1189 1175
1190 1176 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
1191 1177 if isinstance(nodelist, str):
1192 1178 nodelist = [nodelist]
1193 1179 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
1194 1180 state = histeditstate(repo)
1195 1181 state.read()
1196 1182 histedit_nodes = set([repo[rulehash].node() for (action, rulehash)
1197 1183 in state.rules if rulehash in repo])
1198 1184 strip_nodes = set([repo[n].node() for n in nodelist])
1199 1185 common_nodes = histedit_nodes & strip_nodes
1200 1186 if common_nodes:
1201 1187 raise util.Abort(_("histedit in progress, can't strip %s")
1202 1188 % ', '.join(node.short(x) for x in common_nodes))
1203 1189 return orig(ui, repo, nodelist, *args, **kwargs)
1204 1190
1205 1191 extensions.wrapfunction(repair, 'strip', stripwrapper)
1206 1192
1207 1193 def summaryhook(ui, repo):
1208 1194 if not os.path.exists(repo.join('histedit-state')):
1209 1195 return
1210 1196 state = histeditstate(repo)
1211 1197 state.read()
1212 1198 if state.rules:
1213 1199 # i18n: column positioning for "hg summary"
1214 1200 ui.write(_('hist: %s (histedit --continue)\n') %
1215 1201 (ui.label(_('%d remaining'), 'histedit.remaining') %
1216 1202 len(state.rules)))
1217 1203
1218 1204 def extsetup(ui):
1219 1205 cmdutil.summaryhooks.add('histedit', summaryhook)
1220 1206 cmdutil.unfinishedstates.append(
1221 1207 ['histedit-state', False, True, _('histedit in progress'),
1222 1208 _("use 'hg histedit --continue' or 'hg histedit --abort'")])
@@ -1,347 +1,349
1 1 $ . "$TESTDIR/histedit-helpers.sh"
2 2
3 3 $ cat >> $HGRCPATH <<EOF
4 4 > [extensions]
5 5 > histedit=
6 6 > EOF
7 7
8 8 $ initrepo ()
9 9 > {
10 10 > hg init $1
11 11 > cd $1
12 12 > for x in a b c d e f ; do
13 13 > echo $x$x$x$x$x > $x
14 14 > hg add $x
15 15 > done
16 16 > hg ci -m 'Initial commit'
17 17 > for x in a b c d e f ; do
18 18 > echo $x > $x
19 19 > hg ci -m $x
20 20 > done
21 21 > echo 'I can haz no commute' > e
22 22 > hg ci -m 'does not commute with e'
23 23 > cd ..
24 24 > }
25 25
26 26 $ initrepo r
27 27 $ cd r
28 28 Initial generation of the command files
29 29
30 30 $ EDITED="$TESTTMP/editedhistory"
31 31 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 3 >> $EDITED
32 32 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 4 >> $EDITED
33 33 $ hg log --template 'fold {node|short} {rev} {desc}\n' -r 7 >> $EDITED
34 34 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 5 >> $EDITED
35 35 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 6 >> $EDITED
36 36 $ cat $EDITED
37 37 pick 65a9a84f33fd 3 c
38 38 pick 00f1c5383965 4 d
39 39 fold 39522b764e3d 7 does not commute with e
40 40 pick 7b4e2f4b7bcd 5 e
41 41 pick 500cac37a696 6 f
42 42
43 43 log before edit
44 44 $ hg log --graph
45 45 @ changeset: 7:39522b764e3d
46 46 | tag: tip
47 47 | user: test
48 48 | date: Thu Jan 01 00:00:00 1970 +0000
49 49 | summary: does not commute with e
50 50 |
51 51 o changeset: 6:500cac37a696
52 52 | user: test
53 53 | date: Thu Jan 01 00:00:00 1970 +0000
54 54 | summary: f
55 55 |
56 56 o changeset: 5:7b4e2f4b7bcd
57 57 | user: test
58 58 | date: Thu Jan 01 00:00:00 1970 +0000
59 59 | summary: e
60 60 |
61 61 o changeset: 4:00f1c5383965
62 62 | user: test
63 63 | date: Thu Jan 01 00:00:00 1970 +0000
64 64 | summary: d
65 65 |
66 66 o changeset: 3:65a9a84f33fd
67 67 | user: test
68 68 | date: Thu Jan 01 00:00:00 1970 +0000
69 69 | summary: c
70 70 |
71 71 o changeset: 2:da6535b52e45
72 72 | user: test
73 73 | date: Thu Jan 01 00:00:00 1970 +0000
74 74 | summary: b
75 75 |
76 76 o changeset: 1:c1f09da44841
77 77 | user: test
78 78 | date: Thu Jan 01 00:00:00 1970 +0000
79 79 | summary: a
80 80 |
81 81 o changeset: 0:1715188a53c7
82 82 user: test
83 83 date: Thu Jan 01 00:00:00 1970 +0000
84 84 summary: Initial commit
85 85
86 86
87 87 edit the history
88 88 $ hg histedit 3 --commands $EDITED 2>&1 | fixbundle
89 89 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
90 90 merging e
91 91 warning: conflicts during merge.
92 92 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
93 93 Fix up the change and run hg histedit --continue
94 94
95 95 fix up
96 96 $ echo 'I can haz no commute' > e
97 97 $ hg resolve --mark e
98 98 (no more unresolved files)
99 99 $ cat > cat.py <<EOF
100 100 > import sys
101 101 > print open(sys.argv[1]).read()
102 102 > print
103 103 > print
104 104 > EOF
105 105 $ HGEDITOR="python cat.py" hg histedit --continue 2>&1 | fixbundle | grep -v '2 files removed'
106 106 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
107 107 d
108 108 ***
109 109 does not commute with e
110 110
111 111
112 112
113 113 HG: Enter commit message. Lines beginning with 'HG:' are removed.
114 114 HG: Leave message empty to abort commit.
115 115 HG: --
116 116 HG: user: test
117 117 HG: branch 'default'
118 118 HG: changed d
119 119 HG: changed e
120 120
121 121
122 122
123 123 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
124 124 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
125 125 merging e
126 126 warning: conflicts during merge.
127 127 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
128 128 Fix up the change and run hg histedit --continue
129 129
130 130 just continue this time
131 131 $ hg revert -r 'p1()' e
132 132 $ hg resolve --mark e
133 133 (no more unresolved files)
134 134 $ hg histedit --continue 2>&1 | fixbundle
135 7b4e2f4b7bcd: empty changeset
135 136 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
136 137 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
137 138
138 139 log after edit
139 140 $ hg log --graph
140 141 @ changeset: 5:d9cf42e54966
141 142 | tag: tip
142 143 | user: test
143 144 | date: Thu Jan 01 00:00:00 1970 +0000
144 145 | summary: f
145 146 |
146 147 o changeset: 4:10486af2e984
147 148 | user: test
148 149 | date: Thu Jan 01 00:00:00 1970 +0000
149 150 | summary: d
150 151 |
151 152 o changeset: 3:65a9a84f33fd
152 153 | user: test
153 154 | date: Thu Jan 01 00:00:00 1970 +0000
154 155 | summary: c
155 156 |
156 157 o changeset: 2:da6535b52e45
157 158 | user: test
158 159 | date: Thu Jan 01 00:00:00 1970 +0000
159 160 | summary: b
160 161 |
161 162 o changeset: 1:c1f09da44841
162 163 | user: test
163 164 | date: Thu Jan 01 00:00:00 1970 +0000
164 165 | summary: a
165 166 |
166 167 o changeset: 0:1715188a53c7
167 168 user: test
168 169 date: Thu Jan 01 00:00:00 1970 +0000
169 170 summary: Initial commit
170 171
171 172
172 173 contents of e
173 174 $ hg cat e
174 175 I can haz no commute
175 176
176 177 manifest
177 178 $ hg manifest
178 179 a
179 180 b
180 181 c
181 182 d
182 183 e
183 184 f
184 185
185 186 $ cd ..
186 187
187 188 Repeat test using "roll", not "fold". "roll" folds in changes but drops message
188 189
189 190 $ initrepo r2
190 191 $ cd r2
191 192
192 193 Initial generation of the command files
193 194
194 195 $ EDITED="$TESTTMP/editedhistory.2"
195 196 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 3 >> $EDITED
196 197 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 4 >> $EDITED
197 198 $ hg log --template 'roll {node|short} {rev} {desc}\n' -r 7 >> $EDITED
198 199 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 5 >> $EDITED
199 200 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 6 >> $EDITED
200 201 $ cat $EDITED
201 202 pick 65a9a84f33fd 3 c
202 203 pick 00f1c5383965 4 d
203 204 roll 39522b764e3d 7 does not commute with e
204 205 pick 7b4e2f4b7bcd 5 e
205 206 pick 500cac37a696 6 f
206 207
207 208 log before edit
208 209 $ hg log --graph
209 210 @ changeset: 7:39522b764e3d
210 211 | tag: tip
211 212 | user: test
212 213 | date: Thu Jan 01 00:00:00 1970 +0000
213 214 | summary: does not commute with e
214 215 |
215 216 o changeset: 6:500cac37a696
216 217 | user: test
217 218 | date: Thu Jan 01 00:00:00 1970 +0000
218 219 | summary: f
219 220 |
220 221 o changeset: 5:7b4e2f4b7bcd
221 222 | user: test
222 223 | date: Thu Jan 01 00:00:00 1970 +0000
223 224 | summary: e
224 225 |
225 226 o changeset: 4:00f1c5383965
226 227 | user: test
227 228 | date: Thu Jan 01 00:00:00 1970 +0000
228 229 | summary: d
229 230 |
230 231 o changeset: 3:65a9a84f33fd
231 232 | user: test
232 233 | date: Thu Jan 01 00:00:00 1970 +0000
233 234 | summary: c
234 235 |
235 236 o changeset: 2:da6535b52e45
236 237 | user: test
237 238 | date: Thu Jan 01 00:00:00 1970 +0000
238 239 | summary: b
239 240 |
240 241 o changeset: 1:c1f09da44841
241 242 | user: test
242 243 | date: Thu Jan 01 00:00:00 1970 +0000
243 244 | summary: a
244 245 |
245 246 o changeset: 0:1715188a53c7
246 247 user: test
247 248 date: Thu Jan 01 00:00:00 1970 +0000
248 249 summary: Initial commit
249 250
250 251
251 252 edit the history
252 253 $ hg histedit 3 --commands $EDITED 2>&1 | fixbundle
253 254 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
254 255 merging e
255 256 warning: conflicts during merge.
256 257 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
257 258 Fix up the change and run hg histedit --continue
258 259
259 260 fix up
260 261 $ echo 'I can haz no commute' > e
261 262 $ hg resolve --mark e
262 263 (no more unresolved files)
263 264 $ hg histedit --continue 2>&1 | fixbundle | grep -v '2 files removed'
264 265 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
265 266 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
266 267 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
267 268 merging e
268 269 warning: conflicts during merge.
269 270 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
270 271 Fix up the change and run hg histedit --continue
271 272
272 273 just continue this time
273 274 $ hg revert -r 'p1()' e
274 275 $ hg resolve --mark e
275 276 (no more unresolved files)
276 277 $ hg histedit --continue 2>&1 | fixbundle
278 7b4e2f4b7bcd: empty changeset
277 279 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
278 280 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
279 281
280 282 log after edit
281 283 $ hg log --graph
282 284 @ changeset: 5:e7c4f5d4eb75
283 285 | tag: tip
284 286 | user: test
285 287 | date: Thu Jan 01 00:00:00 1970 +0000
286 288 | summary: f
287 289 |
288 290 o changeset: 4:803d1bb561fc
289 291 | user: test
290 292 | date: Thu Jan 01 00:00:00 1970 +0000
291 293 | summary: d
292 294 |
293 295 o changeset: 3:65a9a84f33fd
294 296 | user: test
295 297 | date: Thu Jan 01 00:00:00 1970 +0000
296 298 | summary: c
297 299 |
298 300 o changeset: 2:da6535b52e45
299 301 | user: test
300 302 | date: Thu Jan 01 00:00:00 1970 +0000
301 303 | summary: b
302 304 |
303 305 o changeset: 1:c1f09da44841
304 306 | user: test
305 307 | date: Thu Jan 01 00:00:00 1970 +0000
306 308 | summary: a
307 309 |
308 310 o changeset: 0:1715188a53c7
309 311 user: test
310 312 date: Thu Jan 01 00:00:00 1970 +0000
311 313 summary: Initial commit
312 314
313 315
314 316 contents of e
315 317 $ hg cat e
316 318 I can haz no commute
317 319
318 320 manifest
319 321 $ hg manifest
320 322 a
321 323 b
322 324 c
323 325 d
324 326 e
325 327 f
326 328
327 329 description is taken from rollup target commit
328 330
329 331 $ hg log --debug --rev 4
330 332 changeset: 4:803d1bb561fceac3129ec778db9da249a3106fc3
331 333 phase: draft
332 334 parent: 3:65a9a84f33fdeb1ad5679b3941ec885d2b24027b
333 335 parent: -1:0000000000000000000000000000000000000000
334 336 manifest: 4:b068a323d969f22af1296ec6a5ea9384cef437ac
335 337 user: test
336 338 date: Thu Jan 01 00:00:00 1970 +0000
337 339 files: d e
338 340 extra: branch=default
339 341 extra: histedit_source=00f1c53839651fa5c76d423606811ea5455a79d0,39522b764e3d26103f08bd1fa2ccd3e3d7dbcf4e
340 342 description:
341 343 d
342 344
343 345
344 346
345 347 done with repo r2
346 348
347 349 $ cd ..
@@ -1,298 +1,300
1 1 $ . "$TESTDIR/histedit-helpers.sh"
2 2
3 3 $ cat >> $HGRCPATH <<EOF
4 4 > [extensions]
5 5 > histedit=
6 6 > EOF
7 7
8 8 $ initrepo ()
9 9 > {
10 10 > hg init $1
11 11 > cd $1
12 12 > for x in a b c d e f ; do
13 13 > echo $x$x$x$x$x > $x
14 14 > hg add $x
15 15 > done
16 16 > hg ci -m 'Initial commit'
17 17 > for x in a b c d e f ; do
18 18 > echo $x > $x
19 19 > hg ci -m $x
20 20 > done
21 21 > echo 'I can haz no commute' > e
22 22 > hg ci -m 'does not commute with e'
23 23 > cd ..
24 24 > }
25 25
26 26 $ initrepo r1
27 27 $ cd r1
28 28
29 29 Initial generation of the command files
30 30
31 31 $ EDITED="$TESTTMP/editedhistory"
32 32 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 3 >> $EDITED
33 33 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 4 >> $EDITED
34 34 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 7 >> $EDITED
35 35 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 5 >> $EDITED
36 36 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 6 >> $EDITED
37 37 $ cat $EDITED
38 38 pick 65a9a84f33fd 3 c
39 39 pick 00f1c5383965 4 d
40 40 pick 39522b764e3d 7 does not commute with e
41 41 pick 7b4e2f4b7bcd 5 e
42 42 pick 500cac37a696 6 f
43 43
44 44 log before edit
45 45 $ hg log --graph
46 46 @ changeset: 7:39522b764e3d
47 47 | tag: tip
48 48 | user: test
49 49 | date: Thu Jan 01 00:00:00 1970 +0000
50 50 | summary: does not commute with e
51 51 |
52 52 o changeset: 6:500cac37a696
53 53 | user: test
54 54 | date: Thu Jan 01 00:00:00 1970 +0000
55 55 | summary: f
56 56 |
57 57 o changeset: 5:7b4e2f4b7bcd
58 58 | user: test
59 59 | date: Thu Jan 01 00:00:00 1970 +0000
60 60 | summary: e
61 61 |
62 62 o changeset: 4:00f1c5383965
63 63 | user: test
64 64 | date: Thu Jan 01 00:00:00 1970 +0000
65 65 | summary: d
66 66 |
67 67 o changeset: 3:65a9a84f33fd
68 68 | user: test
69 69 | date: Thu Jan 01 00:00:00 1970 +0000
70 70 | summary: c
71 71 |
72 72 o changeset: 2:da6535b52e45
73 73 | user: test
74 74 | date: Thu Jan 01 00:00:00 1970 +0000
75 75 | summary: b
76 76 |
77 77 o changeset: 1:c1f09da44841
78 78 | user: test
79 79 | date: Thu Jan 01 00:00:00 1970 +0000
80 80 | summary: a
81 81 |
82 82 o changeset: 0:1715188a53c7
83 83 user: test
84 84 date: Thu Jan 01 00:00:00 1970 +0000
85 85 summary: Initial commit
86 86
87 87
88 88 edit the history
89 89 $ hg histedit 3 --commands $EDITED 2>&1 | fixbundle
90 90 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
91 91 merging e
92 92 warning: conflicts during merge.
93 93 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
94 94 Fix up the change and run hg histedit --continue
95 95
96 96 abort the edit
97 97 $ hg histedit --abort 2>&1 | fixbundle
98 98 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
99 99
100 100
101 101 second edit set
102 102
103 103 $ hg log --graph
104 104 @ changeset: 7:39522b764e3d
105 105 | tag: tip
106 106 | user: test
107 107 | date: Thu Jan 01 00:00:00 1970 +0000
108 108 | summary: does not commute with e
109 109 |
110 110 o changeset: 6:500cac37a696
111 111 | user: test
112 112 | date: Thu Jan 01 00:00:00 1970 +0000
113 113 | summary: f
114 114 |
115 115 o changeset: 5:7b4e2f4b7bcd
116 116 | user: test
117 117 | date: Thu Jan 01 00:00:00 1970 +0000
118 118 | summary: e
119 119 |
120 120 o changeset: 4:00f1c5383965
121 121 | user: test
122 122 | date: Thu Jan 01 00:00:00 1970 +0000
123 123 | summary: d
124 124 |
125 125 o changeset: 3:65a9a84f33fd
126 126 | user: test
127 127 | date: Thu Jan 01 00:00:00 1970 +0000
128 128 | summary: c
129 129 |
130 130 o changeset: 2:da6535b52e45
131 131 | user: test
132 132 | date: Thu Jan 01 00:00:00 1970 +0000
133 133 | summary: b
134 134 |
135 135 o changeset: 1:c1f09da44841
136 136 | user: test
137 137 | date: Thu Jan 01 00:00:00 1970 +0000
138 138 | summary: a
139 139 |
140 140 o changeset: 0:1715188a53c7
141 141 user: test
142 142 date: Thu Jan 01 00:00:00 1970 +0000
143 143 summary: Initial commit
144 144
145 145
146 146 edit the history
147 147 $ hg histedit 3 --commands $EDITED 2>&1 | fixbundle
148 148 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
149 149 merging e
150 150 warning: conflicts during merge.
151 151 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
152 152 Fix up the change and run hg histedit --continue
153 153
154 154 fix up
155 155 $ echo 'I can haz no commute' > e
156 156 $ hg resolve --mark e
157 157 (no more unresolved files)
158 158 $ hg histedit --continue 2>&1 | fixbundle
159 159 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
160 160 merging e
161 161 warning: conflicts during merge.
162 162 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
163 163 Fix up the change and run hg histedit --continue
164 164
165 165 This failure is caused by 7b4e2f4b7bcd "e" not rebasing the non commutative
166 166 former children.
167 167
168 168 just continue this time
169 169 $ hg revert -r 'p1()' e
170 170 $ hg resolve --mark e
171 171 (no more unresolved files)
172 172 $ hg histedit --continue 2>&1 | fixbundle
173 7b4e2f4b7bcd: empty changeset
173 174 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
174 175 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
175 176
176 177 log after edit
177 178 $ hg log --graph
178 179 @ changeset: 6:7efe1373e4bc
179 180 | tag: tip
180 181 | user: test
181 182 | date: Thu Jan 01 00:00:00 1970 +0000
182 183 | summary: f
183 184 |
184 185 o changeset: 5:e334d87a1e55
185 186 | user: test
186 187 | date: Thu Jan 01 00:00:00 1970 +0000
187 188 | summary: does not commute with e
188 189 |
189 190 o changeset: 4:00f1c5383965
190 191 | user: test
191 192 | date: Thu Jan 01 00:00:00 1970 +0000
192 193 | summary: d
193 194 |
194 195 o changeset: 3:65a9a84f33fd
195 196 | user: test
196 197 | date: Thu Jan 01 00:00:00 1970 +0000
197 198 | summary: c
198 199 |
199 200 o changeset: 2:da6535b52e45
200 201 | user: test
201 202 | date: Thu Jan 01 00:00:00 1970 +0000
202 203 | summary: b
203 204 |
204 205 o changeset: 1:c1f09da44841
205 206 | user: test
206 207 | date: Thu Jan 01 00:00:00 1970 +0000
207 208 | summary: a
208 209 |
209 210 o changeset: 0:1715188a53c7
210 211 user: test
211 212 date: Thu Jan 01 00:00:00 1970 +0000
212 213 summary: Initial commit
213 214
214 215
215 216 start over
216 217
217 218 $ cd ..
218 219
219 220 $ initrepo r2
220 221 $ cd r2
221 222 $ rm $EDITED
222 223 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 3 >> $EDITED
223 224 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 4 >> $EDITED
224 225 $ hg log --template 'mess {node|short} {rev} {desc}\n' -r 7 >> $EDITED
225 226 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 5 >> $EDITED
226 227 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 6 >> $EDITED
227 228 $ cat $EDITED
228 229 pick 65a9a84f33fd 3 c
229 230 pick 00f1c5383965 4 d
230 231 mess 39522b764e3d 7 does not commute with e
231 232 pick 7b4e2f4b7bcd 5 e
232 233 pick 500cac37a696 6 f
233 234
234 235 edit the history, this time with a fold action
235 236 $ hg histedit 3 --commands $EDITED 2>&1 | fixbundle
236 237 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
237 238 merging e
238 239 warning: conflicts during merge.
239 240 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
240 241 Fix up the change and run hg histedit --continue
241 242
242 243 $ echo 'I can haz no commute' > e
243 244 $ hg resolve --mark e
244 245 (no more unresolved files)
245 246 $ hg histedit --continue 2>&1 | fixbundle
246 247 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
247 248 merging e
248 249 warning: conflicts during merge.
249 250 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
250 251 Fix up the change and run hg histedit --continue
251 252 second edit also fails, but just continue
252 253 $ hg revert -r 'p1()' e
253 254 $ hg resolve --mark e
254 255 (no more unresolved files)
255 256 $ hg histedit --continue 2>&1 | fixbundle
257 7b4e2f4b7bcd: empty changeset
256 258 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
257 259 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
258 260
259 261 post message fix
260 262 $ hg log --graph
261 263 @ changeset: 6:7efe1373e4bc
262 264 | tag: tip
263 265 | user: test
264 266 | date: Thu Jan 01 00:00:00 1970 +0000
265 267 | summary: f
266 268 |
267 269 o changeset: 5:e334d87a1e55
268 270 | user: test
269 271 | date: Thu Jan 01 00:00:00 1970 +0000
270 272 | summary: does not commute with e
271 273 |
272 274 o changeset: 4:00f1c5383965
273 275 | user: test
274 276 | date: Thu Jan 01 00:00:00 1970 +0000
275 277 | summary: d
276 278 |
277 279 o changeset: 3:65a9a84f33fd
278 280 | user: test
279 281 | date: Thu Jan 01 00:00:00 1970 +0000
280 282 | summary: c
281 283 |
282 284 o changeset: 2:da6535b52e45
283 285 | user: test
284 286 | date: Thu Jan 01 00:00:00 1970 +0000
285 287 | summary: b
286 288 |
287 289 o changeset: 1:c1f09da44841
288 290 | user: test
289 291 | date: Thu Jan 01 00:00:00 1970 +0000
290 292 | summary: a
291 293 |
292 294 o changeset: 0:1715188a53c7
293 295 user: test
294 296 date: Thu Jan 01 00:00:00 1970 +0000
295 297 summary: Initial commit
296 298
297 299
298 300 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now