##// END OF EJS Templates
histedit: hook afterresolvedstates
timeless -
r27627:dcbba68e default
parent child Browse files
Show More
@@ -1,1461 +1,1463 b''
1 1 # histedit.py - interactive history editing for mercurial
2 2 #
3 3 # Copyright 2009 Augie Fackler <raf@durin42.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 """interactive history editing
8 8
9 9 With this extension installed, Mercurial gains one new command: histedit. Usage
10 10 is as follows, assuming the following history::
11 11
12 12 @ 3[tip] 7c2fd3b9020c 2009-04-27 18:04 -0500 durin42
13 13 | Add delta
14 14 |
15 15 o 2 030b686bedc4 2009-04-27 18:04 -0500 durin42
16 16 | Add gamma
17 17 |
18 18 o 1 c561b4e977df 2009-04-27 18:04 -0500 durin42
19 19 | Add beta
20 20 |
21 21 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
22 22 Add alpha
23 23
24 24 If you were to run ``hg histedit c561b4e977df``, you would see the following
25 25 file open in your editor::
26 26
27 27 pick c561b4e977df Add beta
28 28 pick 030b686bedc4 Add gamma
29 29 pick 7c2fd3b9020c Add delta
30 30
31 31 # Edit history between 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 commit 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 commit 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 Config
147 147 ------
148 148
149 149 Histedit rule lines are truncated to 80 characters by default. You
150 150 can customize this behavior by setting a different length in your
151 151 configuration file::
152 152
153 153 [histedit]
154 154 linelen = 120 # truncate rule lines at 120 characters
155 155
156 156 ``hg histedit`` attempts to automatically choose an appropriate base
157 157 revision to use. To change which base revision is used, define a
158 158 revset in your configuration file::
159 159
160 160 [histedit]
161 161 defaultrev = only(.) & draft()
162 162
163 163 By default each edited revision needs to be present in histedit commands.
164 164 To remove revision you need to use ``drop`` operation. You can configure
165 165 the drop to be implicit for missing commits by adding:
166 166
167 167 [histedit]
168 168 dropmissing = True
169 169
170 170 """
171 171
172 172 import pickle
173 173 import errno
174 174 import os
175 175 import sys
176 176
177 177 from mercurial import bundle2
178 178 from mercurial import cmdutil
179 179 from mercurial import discovery
180 180 from mercurial import error
181 181 from mercurial import copies
182 182 from mercurial import context
183 183 from mercurial import destutil
184 184 from mercurial import exchange
185 185 from mercurial import extensions
186 186 from mercurial import hg
187 187 from mercurial import node
188 188 from mercurial import repair
189 189 from mercurial import scmutil
190 190 from mercurial import util
191 191 from mercurial import obsolete
192 192 from mercurial import merge as mergemod
193 193 from mercurial.lock import release
194 194 from mercurial.i18n import _
195 195
196 196 cmdtable = {}
197 197 command = cmdutil.command(cmdtable)
198 198
199 199 class _constraints(object):
200 200 # aborts if there are multiple rules for one node
201 201 noduplicates = 'noduplicates'
202 202 # abort if the node does belong to edited stack
203 203 forceother = 'forceother'
204 204 # abort if the node doesn't belong to edited stack
205 205 noother = 'noother'
206 206
207 207 @classmethod
208 208 def known(cls):
209 209 return set([v for k, v in cls.__dict__.items() if k[0] != '_'])
210 210
211 211 # Note for extension authors: ONLY specify testedwith = 'internal' for
212 212 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
213 213 # be specifying the version(s) of Mercurial they are tested with, or
214 214 # leave the attribute unspecified.
215 215 testedwith = 'internal'
216 216
217 217 # i18n: command names and abbreviations must remain untranslated
218 218 editcomment = _("""# Edit history between %s and %s
219 219 #
220 220 # Commits are listed from least to most recent
221 221 #
222 222 # Commands:
223 223 # p, pick = use commit
224 224 # e, edit = use commit, but stop for amending
225 225 # f, fold = use commit, but combine it with the one above
226 226 # r, roll = like fold, but discard this commit's description
227 227 # d, drop = remove commit from history
228 228 # m, mess = edit commit message without changing commit content
229 229 #
230 230 """)
231 231
232 232 class histeditstate(object):
233 233 def __init__(self, repo, parentctxnode=None, actions=None, keep=None,
234 234 topmost=None, replacements=None, lock=None, wlock=None):
235 235 self.repo = repo
236 236 self.actions = actions
237 237 self.keep = keep
238 238 self.topmost = topmost
239 239 self.parentctxnode = parentctxnode
240 240 self.lock = lock
241 241 self.wlock = wlock
242 242 self.backupfile = None
243 243 if replacements is None:
244 244 self.replacements = []
245 245 else:
246 246 self.replacements = replacements
247 247
248 248 def read(self):
249 249 """Load histedit state from disk and set fields appropriately."""
250 250 try:
251 251 state = self.repo.vfs.read('histedit-state')
252 252 except IOError as err:
253 253 if err.errno != errno.ENOENT:
254 254 raise
255 255 raise error.Abort(_('no histedit in progress'))
256 256
257 257 if state.startswith('v1\n'):
258 258 data = self._load()
259 259 parentctxnode, rules, keep, topmost, replacements, backupfile = data
260 260 else:
261 261 data = pickle.loads(state)
262 262 parentctxnode, rules, keep, topmost, replacements = data
263 263 backupfile = None
264 264
265 265 self.parentctxnode = parentctxnode
266 266 rules = "\n".join(["%s %s" % (verb, rest) for [verb, rest] in rules])
267 267 actions = parserules(rules, self)
268 268 self.actions = actions
269 269 self.keep = keep
270 270 self.topmost = topmost
271 271 self.replacements = replacements
272 272 self.backupfile = backupfile
273 273
274 274 def write(self):
275 275 fp = self.repo.vfs('histedit-state', 'w')
276 276 fp.write('v1\n')
277 277 fp.write('%s\n' % node.hex(self.parentctxnode))
278 278 fp.write('%s\n' % node.hex(self.topmost))
279 279 fp.write('%s\n' % self.keep)
280 280 fp.write('%d\n' % len(self.actions))
281 281 for action in self.actions:
282 282 fp.write('%s\n' % action.tostate())
283 283 fp.write('%d\n' % len(self.replacements))
284 284 for replacement in self.replacements:
285 285 fp.write('%s%s\n' % (node.hex(replacement[0]), ''.join(node.hex(r)
286 286 for r in replacement[1])))
287 287 backupfile = self.backupfile
288 288 if not backupfile:
289 289 backupfile = ''
290 290 fp.write('%s\n' % backupfile)
291 291 fp.close()
292 292
293 293 def _load(self):
294 294 fp = self.repo.vfs('histedit-state', 'r')
295 295 lines = [l[:-1] for l in fp.readlines()]
296 296
297 297 index = 0
298 298 lines[index] # version number
299 299 index += 1
300 300
301 301 parentctxnode = node.bin(lines[index])
302 302 index += 1
303 303
304 304 topmost = node.bin(lines[index])
305 305 index += 1
306 306
307 307 keep = lines[index] == 'True'
308 308 index += 1
309 309
310 310 # Rules
311 311 rules = []
312 312 rulelen = int(lines[index])
313 313 index += 1
314 314 for i in xrange(rulelen):
315 315 ruleaction = lines[index]
316 316 index += 1
317 317 rule = lines[index]
318 318 index += 1
319 319 rules.append((ruleaction, rule))
320 320
321 321 # Replacements
322 322 replacements = []
323 323 replacementlen = int(lines[index])
324 324 index += 1
325 325 for i in xrange(replacementlen):
326 326 replacement = lines[index]
327 327 original = node.bin(replacement[:40])
328 328 succ = [node.bin(replacement[i:i + 40]) for i in
329 329 range(40, len(replacement), 40)]
330 330 replacements.append((original, succ))
331 331 index += 1
332 332
333 333 backupfile = lines[index]
334 334 index += 1
335 335
336 336 fp.close()
337 337
338 338 return parentctxnode, rules, keep, topmost, replacements, backupfile
339 339
340 340 def clear(self):
341 341 if self.inprogress():
342 342 self.repo.vfs.unlink('histedit-state')
343 343
344 344 def inprogress(self):
345 345 return self.repo.vfs.exists('histedit-state')
346 346
347 347
348 348 class histeditaction(object):
349 349 def __init__(self, state, node):
350 350 self.state = state
351 351 self.repo = state.repo
352 352 self.node = node
353 353
354 354 @classmethod
355 355 def fromrule(cls, state, rule):
356 356 """Parses the given rule, returning an instance of the histeditaction.
357 357 """
358 358 rulehash = rule.strip().split(' ', 1)[0]
359 359 try:
360 360 rev = node.bin(rulehash)
361 361 except TypeError:
362 362 raise error.ParseError("invalid changeset %s" % rulehash)
363 363 return cls(state, rev)
364 364
365 365 def verify(self, prev):
366 366 """ Verifies semantic correctness of the rule"""
367 367 repo = self.repo
368 368 ha = node.hex(self.node)
369 369 try:
370 370 self.node = repo[ha].node()
371 371 except error.RepoError:
372 372 raise error.ParseError(_('unknown changeset %s listed')
373 373 % ha[:12])
374 374
375 375 def torule(self):
376 376 """build a histedit rule line for an action
377 377
378 378 by default lines are in the form:
379 379 <hash> <rev> <summary>
380 380 """
381 381 ctx = self.repo[self.node]
382 382 summary = ''
383 383 if ctx.description():
384 384 summary = ctx.description().splitlines()[0]
385 385 line = '%s %s %d %s' % (self.verb, ctx, ctx.rev(), summary)
386 386 # trim to 75 columns by default so it's not stupidly wide in my editor
387 387 # (the 5 more are left for verb)
388 388 maxlen = self.repo.ui.configint('histedit', 'linelen', default=80)
389 389 maxlen = max(maxlen, 22) # avoid truncating hash
390 390 return util.ellipsis(line, maxlen)
391 391
392 392 def tostate(self):
393 393 """Print an action in format used by histedit state files
394 394 (the first line is a verb, the remainder is the second)
395 395 """
396 396 return "%s\n%s" % (self.verb, node.hex(self.node))
397 397
398 398 def constraints(self):
399 399 """Return a set of constrains that this action should be verified for
400 400 """
401 401 return set([_constraints.noduplicates, _constraints.noother])
402 402
403 403 def nodetoverify(self):
404 404 """Returns a node associated with the action that will be used for
405 405 verification purposes.
406 406
407 407 If the action doesn't correspond to node it should return None
408 408 """
409 409 return self.node
410 410
411 411 def run(self):
412 412 """Runs the action. The default behavior is simply apply the action's
413 413 rulectx onto the current parentctx."""
414 414 self.applychange()
415 415 self.continuedirty()
416 416 return self.continueclean()
417 417
418 418 def applychange(self):
419 419 """Applies the changes from this action's rulectx onto the current
420 420 parentctx, but does not commit them."""
421 421 repo = self.repo
422 422 rulectx = repo[self.node]
423 423 hg.update(repo, self.state.parentctxnode, quietempty=True)
424 424 stats = applychanges(repo.ui, repo, rulectx, {})
425 425 if stats and stats[3] > 0:
426 426 raise error.InterventionRequired(_('Fix up the change and run '
427 427 'hg histedit --continue'))
428 428
429 429 def continuedirty(self):
430 430 """Continues the action when changes have been applied to the working
431 431 copy. The default behavior is to commit the dirty changes."""
432 432 repo = self.repo
433 433 rulectx = repo[self.node]
434 434
435 435 editor = self.commiteditor()
436 436 commit = commitfuncfor(repo, rulectx)
437 437
438 438 commit(text=rulectx.description(), user=rulectx.user(),
439 439 date=rulectx.date(), extra=rulectx.extra(), editor=editor)
440 440
441 441 def commiteditor(self):
442 442 """The editor to be used to edit the commit message."""
443 443 return False
444 444
445 445 def continueclean(self):
446 446 """Continues the action when the working copy is clean. The default
447 447 behavior is to accept the current commit as the new version of the
448 448 rulectx."""
449 449 ctx = self.repo['.']
450 450 if ctx.node() == self.state.parentctxnode:
451 451 self.repo.ui.warn(_('%s: empty changeset\n') %
452 452 node.short(self.node))
453 453 return ctx, [(self.node, tuple())]
454 454 if ctx.node() == self.node:
455 455 # Nothing changed
456 456 return ctx, []
457 457 return ctx, [(self.node, (ctx.node(),))]
458 458
459 459 def commitfuncfor(repo, src):
460 460 """Build a commit function for the replacement of <src>
461 461
462 462 This function ensure we apply the same treatment to all changesets.
463 463
464 464 - Add a 'histedit_source' entry in extra.
465 465
466 466 Note that fold has its own separated logic because its handling is a bit
467 467 different and not easily factored out of the fold method.
468 468 """
469 469 phasemin = src.phase()
470 470 def commitfunc(**kwargs):
471 471 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
472 472 try:
473 473 repo.ui.setconfig('phases', 'new-commit', phasemin,
474 474 'histedit')
475 475 extra = kwargs.get('extra', {}).copy()
476 476 extra['histedit_source'] = src.hex()
477 477 kwargs['extra'] = extra
478 478 return repo.commit(**kwargs)
479 479 finally:
480 480 repo.ui.restoreconfig(phasebackup)
481 481 return commitfunc
482 482
483 483 def applychanges(ui, repo, ctx, opts):
484 484 """Merge changeset from ctx (only) in the current working directory"""
485 485 wcpar = repo.dirstate.parents()[0]
486 486 if ctx.p1().node() == wcpar:
487 487 # edits are "in place" we do not need to make any merge,
488 488 # just applies changes on parent for editing
489 489 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
490 490 stats = None
491 491 else:
492 492 try:
493 493 # ui.forcemerge is an internal variable, do not document
494 494 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
495 495 'histedit')
496 496 stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'histedit'])
497 497 finally:
498 498 repo.ui.setconfig('ui', 'forcemerge', '', 'histedit')
499 499 return stats
500 500
501 501 def collapse(repo, first, last, commitopts, skipprompt=False):
502 502 """collapse the set of revisions from first to last as new one.
503 503
504 504 Expected commit options are:
505 505 - message
506 506 - date
507 507 - username
508 508 Commit message is edited in all cases.
509 509
510 510 This function works in memory."""
511 511 ctxs = list(repo.set('%d::%d', first, last))
512 512 if not ctxs:
513 513 return None
514 514 for c in ctxs:
515 515 if not c.mutable():
516 516 raise error.ParseError(
517 517 _("cannot fold into public change %s") % node.short(c.node()))
518 518 base = first.parents()[0]
519 519
520 520 # commit a new version of the old changeset, including the update
521 521 # collect all files which might be affected
522 522 files = set()
523 523 for ctx in ctxs:
524 524 files.update(ctx.files())
525 525
526 526 # Recompute copies (avoid recording a -> b -> a)
527 527 copied = copies.pathcopies(base, last)
528 528
529 529 # prune files which were reverted by the updates
530 530 def samefile(f):
531 531 if f in last.manifest():
532 532 a = last.filectx(f)
533 533 if f in base.manifest():
534 534 b = base.filectx(f)
535 535 return (a.data() == b.data()
536 536 and a.flags() == b.flags())
537 537 else:
538 538 return False
539 539 else:
540 540 return f not in base.manifest()
541 541 files = [f for f in files if not samefile(f)]
542 542 # commit version of these files as defined by head
543 543 headmf = last.manifest()
544 544 def filectxfn(repo, ctx, path):
545 545 if path in headmf:
546 546 fctx = last[path]
547 547 flags = fctx.flags()
548 548 mctx = context.memfilectx(repo,
549 549 fctx.path(), fctx.data(),
550 550 islink='l' in flags,
551 551 isexec='x' in flags,
552 552 copied=copied.get(path))
553 553 return mctx
554 554 return None
555 555
556 556 if commitopts.get('message'):
557 557 message = commitopts['message']
558 558 else:
559 559 message = first.description()
560 560 user = commitopts.get('user')
561 561 date = commitopts.get('date')
562 562 extra = commitopts.get('extra')
563 563
564 564 parents = (first.p1().node(), first.p2().node())
565 565 editor = None
566 566 if not skipprompt:
567 567 editor = cmdutil.getcommiteditor(edit=True, editform='histedit.fold')
568 568 new = context.memctx(repo,
569 569 parents=parents,
570 570 text=message,
571 571 files=files,
572 572 filectxfn=filectxfn,
573 573 user=user,
574 574 date=date,
575 575 extra=extra,
576 576 editor=editor)
577 577 return repo.commitctx(new)
578 578
579 579 def _isdirtywc(repo):
580 580 return repo[None].dirty(missing=True)
581 581
582 582 def abortdirty():
583 583 raise error.Abort(_('working copy has pending changes'),
584 584 hint=_('amend, commit, or revert them and run histedit '
585 585 '--continue, or abort with histedit --abort'))
586 586
587 587
588 588 actiontable = {}
589 589 actionlist = []
590 590
591 591 def addhisteditaction(verbs):
592 592 def wrap(cls):
593 593 cls.verb = verbs[0]
594 594 for verb in verbs:
595 595 actiontable[verb] = cls
596 596 actionlist.append(cls)
597 597 return cls
598 598 return wrap
599 599
600 600
601 601 @addhisteditaction(['pick', 'p'])
602 602 class pick(histeditaction):
603 603 def run(self):
604 604 rulectx = self.repo[self.node]
605 605 if rulectx.parents()[0].node() == self.state.parentctxnode:
606 606 self.repo.ui.debug('node %s unchanged\n' % node.short(self.node))
607 607 return rulectx, []
608 608
609 609 return super(pick, self).run()
610 610
611 611 @addhisteditaction(['edit', 'e'])
612 612 class edit(histeditaction):
613 613 def run(self):
614 614 repo = self.repo
615 615 rulectx = repo[self.node]
616 616 hg.update(repo, self.state.parentctxnode, quietempty=True)
617 617 applychanges(repo.ui, repo, rulectx, {})
618 618 raise error.InterventionRequired(
619 619 _('Make changes as needed, you may commit or record as needed '
620 620 'now.\nWhen you are finished, run hg histedit --continue to '
621 621 'resume.'))
622 622
623 623 def commiteditor(self):
624 624 return cmdutil.getcommiteditor(edit=True, editform='histedit.edit')
625 625
626 626 @addhisteditaction(['fold', 'f'])
627 627 class fold(histeditaction):
628 628 def verify(self, prev):
629 629 """ Verifies semantic correctness of the fold rule"""
630 630 super(fold, self).verify(prev)
631 631 repo = self.repo
632 632 if not prev:
633 633 c = repo[self.node].parents()[0]
634 634 elif not prev.verb in ('pick', 'base'):
635 635 return
636 636 else:
637 637 c = repo[prev.node]
638 638 if not c.mutable():
639 639 raise error.ParseError(
640 640 _("cannot fold into public change %s") % node.short(c.node()))
641 641
642 642
643 643 def continuedirty(self):
644 644 repo = self.repo
645 645 rulectx = repo[self.node]
646 646
647 647 commit = commitfuncfor(repo, rulectx)
648 648 commit(text='fold-temp-revision %s' % node.short(self.node),
649 649 user=rulectx.user(), date=rulectx.date(),
650 650 extra=rulectx.extra())
651 651
652 652 def continueclean(self):
653 653 repo = self.repo
654 654 ctx = repo['.']
655 655 rulectx = repo[self.node]
656 656 parentctxnode = self.state.parentctxnode
657 657 if ctx.node() == parentctxnode:
658 658 repo.ui.warn(_('%s: empty changeset\n') %
659 659 node.short(self.node))
660 660 return ctx, [(self.node, (parentctxnode,))]
661 661
662 662 parentctx = repo[parentctxnode]
663 663 newcommits = set(c.node() for c in repo.set('(%d::. - %d)', parentctx,
664 664 parentctx))
665 665 if not newcommits:
666 666 repo.ui.warn(_('%s: cannot fold - working copy is not a '
667 667 'descendant of previous commit %s\n') %
668 668 (node.short(self.node), node.short(parentctxnode)))
669 669 return ctx, [(self.node, (ctx.node(),))]
670 670
671 671 middlecommits = newcommits.copy()
672 672 middlecommits.discard(ctx.node())
673 673
674 674 return self.finishfold(repo.ui, repo, parentctx, rulectx, ctx.node(),
675 675 middlecommits)
676 676
677 677 def skipprompt(self):
678 678 """Returns true if the rule should skip the message editor.
679 679
680 680 For example, 'fold' wants to show an editor, but 'rollup'
681 681 doesn't want to.
682 682 """
683 683 return False
684 684
685 685 def mergedescs(self):
686 686 """Returns true if the rule should merge messages of multiple changes.
687 687
688 688 This exists mainly so that 'rollup' rules can be a subclass of
689 689 'fold'.
690 690 """
691 691 return True
692 692
693 693 def finishfold(self, ui, repo, ctx, oldctx, newnode, internalchanges):
694 694 parent = ctx.parents()[0].node()
695 695 hg.update(repo, parent)
696 696 ### prepare new commit data
697 697 commitopts = {}
698 698 commitopts['user'] = ctx.user()
699 699 # commit message
700 700 if not self.mergedescs():
701 701 newmessage = ctx.description()
702 702 else:
703 703 newmessage = '\n***\n'.join(
704 704 [ctx.description()] +
705 705 [repo[r].description() for r in internalchanges] +
706 706 [oldctx.description()]) + '\n'
707 707 commitopts['message'] = newmessage
708 708 # date
709 709 commitopts['date'] = max(ctx.date(), oldctx.date())
710 710 extra = ctx.extra().copy()
711 711 # histedit_source
712 712 # note: ctx is likely a temporary commit but that the best we can do
713 713 # here. This is sufficient to solve issue3681 anyway.
714 714 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex())
715 715 commitopts['extra'] = extra
716 716 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
717 717 try:
718 718 phasemin = max(ctx.phase(), oldctx.phase())
719 719 repo.ui.setconfig('phases', 'new-commit', phasemin, 'histedit')
720 720 n = collapse(repo, ctx, repo[newnode], commitopts,
721 721 skipprompt=self.skipprompt())
722 722 finally:
723 723 repo.ui.restoreconfig(phasebackup)
724 724 if n is None:
725 725 return ctx, []
726 726 hg.update(repo, n)
727 727 replacements = [(oldctx.node(), (newnode,)),
728 728 (ctx.node(), (n,)),
729 729 (newnode, (n,)),
730 730 ]
731 731 for ich in internalchanges:
732 732 replacements.append((ich, (n,)))
733 733 return repo[n], replacements
734 734
735 735 class base(histeditaction):
736 736 def constraints(self):
737 737 return set([_constraints.forceother])
738 738
739 739 def run(self):
740 740 if self.repo['.'].node() != self.node:
741 741 mergemod.update(self.repo, self.node, False, True)
742 742 # branchmerge, force)
743 743 return self.continueclean()
744 744
745 745 def continuedirty(self):
746 746 abortdirty()
747 747
748 748 def continueclean(self):
749 749 basectx = self.repo['.']
750 750 return basectx, []
751 751
752 752 @addhisteditaction(['_multifold'])
753 753 class _multifold(fold):
754 754 """fold subclass used for when multiple folds happen in a row
755 755
756 756 We only want to fire the editor for the folded message once when
757 757 (say) four changes are folded down into a single change. This is
758 758 similar to rollup, but we should preserve both messages so that
759 759 when the last fold operation runs we can show the user all the
760 760 commit messages in their editor.
761 761 """
762 762 def skipprompt(self):
763 763 return True
764 764
765 765 @addhisteditaction(["roll", "r"])
766 766 class rollup(fold):
767 767 def mergedescs(self):
768 768 return False
769 769
770 770 def skipprompt(self):
771 771 return True
772 772
773 773 @addhisteditaction(["drop", "d"])
774 774 class drop(histeditaction):
775 775 def run(self):
776 776 parentctx = self.repo[self.state.parentctxnode]
777 777 return parentctx, [(self.node, tuple())]
778 778
779 779 @addhisteditaction(["mess", "m"])
780 780 class message(histeditaction):
781 781 def commiteditor(self):
782 782 return cmdutil.getcommiteditor(edit=True, editform='histedit.mess')
783 783
784 784 def findoutgoing(ui, repo, remote=None, force=False, opts=None):
785 785 """utility function to find the first outgoing changeset
786 786
787 787 Used by initialization code"""
788 788 if opts is None:
789 789 opts = {}
790 790 dest = ui.expandpath(remote or 'default-push', remote or 'default')
791 791 dest, revs = hg.parseurl(dest, None)[:2]
792 792 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
793 793
794 794 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
795 795 other = hg.peer(repo, opts, dest)
796 796
797 797 if revs:
798 798 revs = [repo.lookup(rev) for rev in revs]
799 799
800 800 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
801 801 if not outgoing.missing:
802 802 raise error.Abort(_('no outgoing ancestors'))
803 803 roots = list(repo.revs("roots(%ln)", outgoing.missing))
804 804 if 1 < len(roots):
805 805 msg = _('there are ambiguous outgoing revisions')
806 806 hint = _('see "hg help histedit" for more detail')
807 807 raise error.Abort(msg, hint=hint)
808 808 return repo.lookup(roots[0])
809 809
810 810
811 811 @command('histedit',
812 812 [('', 'commands', '',
813 813 _('read history edits from the specified file'), _('FILE')),
814 814 ('c', 'continue', False, _('continue an edit already in progress')),
815 815 ('', 'edit-plan', False, _('edit remaining actions list')),
816 816 ('k', 'keep', False,
817 817 _("don't strip old nodes after edit is complete")),
818 818 ('', 'abort', False, _('abort an edit in progress')),
819 819 ('o', 'outgoing', False, _('changesets not found in destination')),
820 820 ('f', 'force', False,
821 821 _('force outgoing even for unrelated repositories')),
822 822 ('r', 'rev', [], _('first revision to be edited'), _('REV'))],
823 823 _("[ANCESTOR] | --outgoing [URL]"))
824 824 def histedit(ui, repo, *freeargs, **opts):
825 825 """interactively edit changeset history
826 826
827 827 This command edits changesets between an ANCESTOR and the parent of
828 828 the working directory.
829 829
830 830 The value from the "histedit.defaultrev" config option is used as a
831 831 revset to select the base revision when ANCESTOR is not specified.
832 832 The first revision returned by the revset is used. By default, this
833 833 selects the editable history that is unique to the ancestry of the
834 834 working directory.
835 835
836 836 With --outgoing, this edits changesets not found in the
837 837 destination repository. If URL of the destination is omitted, the
838 838 'default-push' (or 'default') path will be used.
839 839
840 840 For safety, this command is also aborted if there are ambiguous
841 841 outgoing revisions which may confuse users: for example, if there
842 842 are multiple branches containing outgoing revisions.
843 843
844 844 Use "min(outgoing() and ::.)" or similar revset specification
845 845 instead of --outgoing to specify edit target revision exactly in
846 846 such ambiguous situation. See :hg:`help revsets` for detail about
847 847 selecting revisions.
848 848
849 849 .. container:: verbose
850 850
851 851 Examples:
852 852
853 853 - A number of changes have been made.
854 854 Revision 3 is no longer needed.
855 855
856 856 Start history editing from revision 3::
857 857
858 858 hg histedit -r 3
859 859
860 860 An editor opens, containing the list of revisions,
861 861 with specific actions specified::
862 862
863 863 pick 5339bf82f0ca 3 Zworgle the foobar
864 864 pick 8ef592ce7cc4 4 Bedazzle the zerlog
865 865 pick 0a9639fcda9d 5 Morgify the cromulancy
866 866
867 867 Additional information about the possible actions
868 868 to take appears below the list of revisions.
869 869
870 870 To remove revision 3 from the history,
871 871 its action (at the beginning of the relevant line)
872 872 is changed to 'drop'::
873 873
874 874 drop 5339bf82f0ca 3 Zworgle the foobar
875 875 pick 8ef592ce7cc4 4 Bedazzle the zerlog
876 876 pick 0a9639fcda9d 5 Morgify the cromulancy
877 877
878 878 - A number of changes have been made.
879 879 Revision 2 and 4 need to be swapped.
880 880
881 881 Start history editing from revision 2::
882 882
883 883 hg histedit -r 2
884 884
885 885 An editor opens, containing the list of revisions,
886 886 with specific actions specified::
887 887
888 888 pick 252a1af424ad 2 Blorb a morgwazzle
889 889 pick 5339bf82f0ca 3 Zworgle the foobar
890 890 pick 8ef592ce7cc4 4 Bedazzle the zerlog
891 891
892 892 To swap revision 2 and 4, its lines are swapped
893 893 in the editor::
894 894
895 895 pick 8ef592ce7cc4 4 Bedazzle the zerlog
896 896 pick 5339bf82f0ca 3 Zworgle the foobar
897 897 pick 252a1af424ad 2 Blorb a morgwazzle
898 898
899 899 Returns 0 on success, 1 if user intervention is required (not only
900 900 for intentional "edit" command, but also for resolving unexpected
901 901 conflicts).
902 902 """
903 903 state = histeditstate(repo)
904 904 try:
905 905 state.wlock = repo.wlock()
906 906 state.lock = repo.lock()
907 907 _histedit(ui, repo, state, *freeargs, **opts)
908 908 finally:
909 909 release(state.lock, state.wlock)
910 910
911 911 def _histedit(ui, repo, state, *freeargs, **opts):
912 912 # TODO only abort if we try to histedit mq patches, not just
913 913 # blanket if mq patches are applied somewhere
914 914 mq = getattr(repo, 'mq', None)
915 915 if mq and mq.applied:
916 916 raise error.Abort(_('source has mq patches applied'))
917 917
918 918 # basic argument incompatibility processing
919 919 outg = opts.get('outgoing')
920 920 cont = opts.get('continue')
921 921 editplan = opts.get('edit_plan')
922 922 abort = opts.get('abort')
923 923 force = opts.get('force')
924 924 rules = opts.get('commands', '')
925 925 revs = opts.get('rev', [])
926 926 goal = 'new' # This invocation goal, in new, continue, abort
927 927 if force and not outg:
928 928 raise error.Abort(_('--force only allowed with --outgoing'))
929 929 if cont:
930 930 if any((outg, abort, revs, freeargs, rules, editplan)):
931 931 raise error.Abort(_('no arguments allowed with --continue'))
932 932 goal = 'continue'
933 933 elif abort:
934 934 if any((outg, revs, freeargs, rules, editplan)):
935 935 raise error.Abort(_('no arguments allowed with --abort'))
936 936 goal = 'abort'
937 937 elif editplan:
938 938 if any((outg, revs, freeargs)):
939 939 raise error.Abort(_('only --commands argument allowed with '
940 940 '--edit-plan'))
941 941 goal = 'edit-plan'
942 942 else:
943 943 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
944 944 raise error.Abort(_('history edit already in progress, try '
945 945 '--continue or --abort'))
946 946 if outg:
947 947 if revs:
948 948 raise error.Abort(_('no revisions allowed with --outgoing'))
949 949 if len(freeargs) > 1:
950 950 raise error.Abort(
951 951 _('only one repo argument allowed with --outgoing'))
952 952 else:
953 953 revs.extend(freeargs)
954 954 if len(revs) == 0:
955 955 defaultrev = destutil.desthistedit(ui, repo)
956 956 if defaultrev is not None:
957 957 revs.append(defaultrev)
958 958
959 959 if len(revs) != 1:
960 960 raise error.Abort(
961 961 _('histedit requires exactly one ancestor revision'))
962 962
963 963
964 964 replacements = []
965 965 state.keep = opts.get('keep', False)
966 966 supportsmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
967 967
968 968 # rebuild state
969 969 if goal == 'continue':
970 970 state.read()
971 971 state = bootstrapcontinue(ui, state, opts)
972 972 elif goal == 'edit-plan':
973 973 state.read()
974 974 if not rules:
975 975 comment = editcomment % (node.short(state.parentctxnode),
976 976 node.short(state.topmost))
977 977 rules = ruleeditor(repo, ui, state.actions, comment)
978 978 else:
979 979 if rules == '-':
980 980 f = sys.stdin
981 981 else:
982 982 f = open(rules)
983 983 rules = f.read()
984 984 f.close()
985 985 actions = parserules(rules, state)
986 986 ctxs = [repo[act.nodetoverify()] \
987 987 for act in state.actions if act.nodetoverify()]
988 988 warnverifyactions(ui, repo, actions, state, ctxs)
989 989 state.actions = actions
990 990 state.write()
991 991 return
992 992 elif goal == 'abort':
993 993 try:
994 994 state.read()
995 995 tmpnodes, leafs = newnodestoabort(state)
996 996 ui.debug('restore wc to old parent %s\n'
997 997 % node.short(state.topmost))
998 998
999 999 # Recover our old commits if necessary
1000 1000 if not state.topmost in repo and state.backupfile:
1001 1001 backupfile = repo.join(state.backupfile)
1002 1002 f = hg.openpath(ui, backupfile)
1003 1003 gen = exchange.readbundle(ui, f, backupfile)
1004 1004 tr = repo.transaction('histedit.abort')
1005 1005 try:
1006 1006 if not isinstance(gen, bundle2.unbundle20):
1007 1007 gen.apply(repo, 'histedit', 'bundle:' + backupfile)
1008 1008 if isinstance(gen, bundle2.unbundle20):
1009 1009 bundle2.applybundle(repo, gen, tr,
1010 1010 source='histedit',
1011 1011 url='bundle:' + backupfile)
1012 1012 tr.close()
1013 1013 finally:
1014 1014 tr.release()
1015 1015
1016 1016 os.remove(backupfile)
1017 1017
1018 1018 # check whether we should update away
1019 1019 if repo.unfiltered().revs('parents() and (%n or %ln::)',
1020 1020 state.parentctxnode, leafs | tmpnodes):
1021 1021 hg.clean(repo, state.topmost, show_stats=True, quietempty=True)
1022 1022 cleanupnode(ui, repo, 'created', tmpnodes)
1023 1023 cleanupnode(ui, repo, 'temp', leafs)
1024 1024 except Exception:
1025 1025 if state.inprogress():
1026 1026 ui.warn(_('warning: encountered an exception during histedit '
1027 1027 '--abort; the repository may not have been completely '
1028 1028 'cleaned up\n'))
1029 1029 raise
1030 1030 finally:
1031 1031 state.clear()
1032 1032 return
1033 1033 else:
1034 1034 cmdutil.checkunfinished(repo)
1035 1035 cmdutil.bailifchanged(repo)
1036 1036
1037 1037 topmost, empty = repo.dirstate.parents()
1038 1038 if outg:
1039 1039 if freeargs:
1040 1040 remote = freeargs[0]
1041 1041 else:
1042 1042 remote = None
1043 1043 root = findoutgoing(ui, repo, remote, force, opts)
1044 1044 else:
1045 1045 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs)))
1046 1046 if len(rr) != 1:
1047 1047 raise error.Abort(_('The specified revisions must have '
1048 1048 'exactly one common root'))
1049 1049 root = rr[0].node()
1050 1050
1051 1051 revs = between(repo, root, topmost, state.keep)
1052 1052 if not revs:
1053 1053 raise error.Abort(_('%s is not an ancestor of working directory') %
1054 1054 node.short(root))
1055 1055
1056 1056 ctxs = [repo[r] for r in revs]
1057 1057 if not rules:
1058 1058 comment = editcomment % (node.short(root), node.short(topmost))
1059 1059 actions = [pick(state, r) for r in revs]
1060 1060 rules = ruleeditor(repo, ui, actions, comment)
1061 1061 else:
1062 1062 if rules == '-':
1063 1063 f = sys.stdin
1064 1064 else:
1065 1065 f = open(rules)
1066 1066 rules = f.read()
1067 1067 f.close()
1068 1068 actions = parserules(rules, state)
1069 1069 warnverifyactions(ui, repo, actions, state, ctxs)
1070 1070
1071 1071 parentctxnode = repo[root].parents()[0].node()
1072 1072
1073 1073 state.parentctxnode = parentctxnode
1074 1074 state.actions = actions
1075 1075 state.topmost = topmost
1076 1076 state.replacements = replacements
1077 1077
1078 1078 # Create a backup so we can always abort completely.
1079 1079 backupfile = None
1080 1080 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1081 1081 backupfile = repair._bundle(repo, [parentctxnode], [topmost], root,
1082 1082 'histedit')
1083 1083 state.backupfile = backupfile
1084 1084
1085 1085 # preprocess rules so that we can hide inner folds from the user
1086 1086 # and only show one editor
1087 1087 actions = state.actions[:]
1088 1088 for idx, (action, nextact) in enumerate(
1089 1089 zip(actions, actions[1:] + [None])):
1090 1090 if action.verb == 'fold' and nextact and nextact.verb == 'fold':
1091 1091 state.actions[idx].__class__ = _multifold
1092 1092
1093 1093 total = len(state.actions)
1094 1094 pos = 0
1095 1095 while state.actions:
1096 1096 state.write()
1097 1097 actobj = state.actions.pop(0)
1098 1098 pos += 1
1099 1099 ui.progress(_("editing"), pos, actobj.torule(),
1100 1100 _('changes'), total)
1101 1101 ui.debug('histedit: processing %s %s\n' % (actobj.verb,\
1102 1102 actobj.torule()))
1103 1103 parentctx, replacement_ = actobj.run()
1104 1104 state.parentctxnode = parentctx.node()
1105 1105 state.replacements.extend(replacement_)
1106 1106 state.write()
1107 1107 ui.progress(_("editing"), None)
1108 1108
1109 1109 hg.update(repo, state.parentctxnode, quietempty=True)
1110 1110
1111 1111 mapping, tmpnodes, created, ntm = processreplacement(state)
1112 1112 if mapping:
1113 1113 for prec, succs in mapping.iteritems():
1114 1114 if not succs:
1115 1115 ui.debug('histedit: %s is dropped\n' % node.short(prec))
1116 1116 else:
1117 1117 ui.debug('histedit: %s is replaced by %s\n' % (
1118 1118 node.short(prec), node.short(succs[0])))
1119 1119 if len(succs) > 1:
1120 1120 m = 'histedit: %s'
1121 1121 for n in succs[1:]:
1122 1122 ui.debug(m % node.short(n))
1123 1123
1124 1124 if supportsmarkers:
1125 1125 # Only create markers if the temp nodes weren't already removed.
1126 1126 obsolete.createmarkers(repo, ((repo[t],()) for t in sorted(tmpnodes)
1127 1127 if t in repo))
1128 1128 else:
1129 1129 cleanupnode(ui, repo, 'temp', tmpnodes)
1130 1130
1131 1131 if not state.keep:
1132 1132 if mapping:
1133 1133 movebookmarks(ui, repo, mapping, state.topmost, ntm)
1134 1134 # TODO update mq state
1135 1135 if supportsmarkers:
1136 1136 markers = []
1137 1137 # sort by revision number because it sound "right"
1138 1138 for prec in sorted(mapping, key=repo.changelog.rev):
1139 1139 succs = mapping[prec]
1140 1140 markers.append((repo[prec],
1141 1141 tuple(repo[s] for s in succs)))
1142 1142 if markers:
1143 1143 obsolete.createmarkers(repo, markers)
1144 1144 else:
1145 1145 cleanupnode(ui, repo, 'replaced', mapping)
1146 1146
1147 1147 state.clear()
1148 1148 if os.path.exists(repo.sjoin('undo')):
1149 1149 os.unlink(repo.sjoin('undo'))
1150 1150 if repo.vfs.exists('histedit-last-edit.txt'):
1151 1151 repo.vfs.unlink('histedit-last-edit.txt')
1152 1152
1153 1153 def bootstrapcontinue(ui, state, opts):
1154 1154 repo = state.repo
1155 1155 if state.actions:
1156 1156 actobj = state.actions.pop(0)
1157 1157
1158 1158 if _isdirtywc(repo):
1159 1159 actobj.continuedirty()
1160 1160 if _isdirtywc(repo):
1161 1161 abortdirty()
1162 1162
1163 1163 parentctx, replacements = actobj.continueclean()
1164 1164
1165 1165 state.parentctxnode = parentctx.node()
1166 1166 state.replacements.extend(replacements)
1167 1167
1168 1168 return state
1169 1169
1170 1170 def between(repo, old, new, keep):
1171 1171 """select and validate the set of revision to edit
1172 1172
1173 1173 When keep is false, the specified set can't have children."""
1174 1174 ctxs = list(repo.set('%n::%n', old, new))
1175 1175 if ctxs and not keep:
1176 1176 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
1177 1177 repo.revs('(%ld::) - (%ld)', ctxs, ctxs)):
1178 1178 raise error.Abort(_('cannot edit history that would orphan nodes'))
1179 1179 if repo.revs('(%ld) and merge()', ctxs):
1180 1180 raise error.Abort(_('cannot edit history that contains merges'))
1181 1181 root = ctxs[0] # list is already sorted by repo.set
1182 1182 if not root.mutable():
1183 1183 raise error.Abort(_('cannot edit public changeset: %s') % root,
1184 1184 hint=_('see "hg help phases" for details'))
1185 1185 return [c.node() for c in ctxs]
1186 1186
1187 1187 def ruleeditor(repo, ui, actions, editcomment=""):
1188 1188 """open an editor to edit rules
1189 1189
1190 1190 rules are in the format [ [act, ctx], ...] like in state.rules
1191 1191 """
1192 1192 rules = '\n'.join([act.torule() for act in actions])
1193 1193 rules += '\n\n'
1194 1194 rules += editcomment
1195 1195 rules = ui.edit(rules, ui.username(), {'prefix': 'histedit'})
1196 1196
1197 1197 # Save edit rules in .hg/histedit-last-edit.txt in case
1198 1198 # the user needs to ask for help after something
1199 1199 # surprising happens.
1200 1200 f = open(repo.join('histedit-last-edit.txt'), 'w')
1201 1201 f.write(rules)
1202 1202 f.close()
1203 1203
1204 1204 return rules
1205 1205
1206 1206 def parserules(rules, state):
1207 1207 """Read the histedit rules string and return list of action objects """
1208 1208 rules = [l for l in (r.strip() for r in rules.splitlines())
1209 1209 if l and not l.startswith('#')]
1210 1210 actions = []
1211 1211 for r in rules:
1212 1212 if ' ' not in r:
1213 1213 raise error.ParseError(_('malformed line "%s"') % r)
1214 1214 verb, rest = r.split(' ', 1)
1215 1215
1216 1216 if verb not in actiontable:
1217 1217 raise error.ParseError(_('unknown action "%s"') % verb)
1218 1218
1219 1219 action = actiontable[verb].fromrule(state, rest)
1220 1220 actions.append(action)
1221 1221 return actions
1222 1222
1223 1223 def warnverifyactions(ui, repo, actions, state, ctxs):
1224 1224 try:
1225 1225 verifyactions(actions, state, ctxs)
1226 1226 except error.ParseError:
1227 1227 if repo.vfs.exists('histedit-last-edit.txt'):
1228 1228 ui.warn(_('warning: histedit rules saved '
1229 1229 'to: .hg/histedit-last-edit.txt\n'))
1230 1230 raise
1231 1231
1232 1232 def verifyactions(actions, state, ctxs):
1233 1233 """Verify that there exists exactly one action per given changeset and
1234 1234 other constraints.
1235 1235
1236 1236 Will abort if there are to many or too few rules, a malformed rule,
1237 1237 or a rule on a changeset outside of the user-given range.
1238 1238 """
1239 1239 expected = set(c.hex() for c in ctxs)
1240 1240 seen = set()
1241 1241 prev = None
1242 1242 for action in actions:
1243 1243 action.verify(prev)
1244 1244 prev = action
1245 1245 constraints = action.constraints()
1246 1246 for constraint in constraints:
1247 1247 if constraint not in _constraints.known():
1248 1248 raise error.ParseError(_('unknown constraint "%s"') %
1249 1249 constraint)
1250 1250
1251 1251 nodetoverify = action.nodetoverify()
1252 1252 if nodetoverify is not None:
1253 1253 ha = node.hex(nodetoverify)
1254 1254 if _constraints.noother in constraints and ha not in expected:
1255 1255 raise error.ParseError(
1256 1256 _('may not use "%s" with changesets '
1257 1257 'other than the ones listed') % action.verb)
1258 1258 if _constraints.forceother in constraints and ha in expected:
1259 1259 raise error.ParseError(
1260 1260 _('may not use "%s" with changesets '
1261 1261 'within the edited list') % action.verb)
1262 1262 if _constraints.noduplicates in constraints and ha in seen:
1263 1263 raise error.ParseError(_(
1264 1264 'duplicated command for changeset %s') %
1265 1265 ha[:12])
1266 1266 seen.add(ha)
1267 1267 missing = sorted(expected - seen) # sort to stabilize output
1268 1268
1269 1269 if state.repo.ui.configbool('histedit', 'dropmissing'):
1270 1270 drops = [drop(state, node.bin(n)) for n in missing]
1271 1271 # put the in the beginning so they execute immediately and
1272 1272 # don't show in the edit-plan in the future
1273 1273 actions[:0] = drops
1274 1274 elif missing:
1275 1275 raise error.ParseError(_('missing rules for changeset %s') %
1276 1276 missing[0][:12],
1277 1277 hint=_('use "drop %s" to discard, see also: '
1278 1278 '"hg help -e histedit.config"') % missing[0][:12])
1279 1279
1280 1280 def newnodestoabort(state):
1281 1281 """process the list of replacements to return
1282 1282
1283 1283 1) the list of final node
1284 1284 2) the list of temporary node
1285 1285
1286 1286 This is meant to be used on abort as less data are required in this case.
1287 1287 """
1288 1288 replacements = state.replacements
1289 1289 allsuccs = set()
1290 1290 replaced = set()
1291 1291 for rep in replacements:
1292 1292 allsuccs.update(rep[1])
1293 1293 replaced.add(rep[0])
1294 1294 newnodes = allsuccs - replaced
1295 1295 tmpnodes = allsuccs & replaced
1296 1296 return newnodes, tmpnodes
1297 1297
1298 1298
1299 1299 def processreplacement(state):
1300 1300 """process the list of replacements to return
1301 1301
1302 1302 1) the final mapping between original and created nodes
1303 1303 2) the list of temporary node created by histedit
1304 1304 3) the list of new commit created by histedit"""
1305 1305 replacements = state.replacements
1306 1306 allsuccs = set()
1307 1307 replaced = set()
1308 1308 fullmapping = {}
1309 1309 # initialize basic set
1310 1310 # fullmapping records all operations recorded in replacement
1311 1311 for rep in replacements:
1312 1312 allsuccs.update(rep[1])
1313 1313 replaced.add(rep[0])
1314 1314 fullmapping.setdefault(rep[0], set()).update(rep[1])
1315 1315 new = allsuccs - replaced
1316 1316 tmpnodes = allsuccs & replaced
1317 1317 # Reduce content fullmapping into direct relation between original nodes
1318 1318 # and final node created during history edition
1319 1319 # Dropped changeset are replaced by an empty list
1320 1320 toproceed = set(fullmapping)
1321 1321 final = {}
1322 1322 while toproceed:
1323 1323 for x in list(toproceed):
1324 1324 succs = fullmapping[x]
1325 1325 for s in list(succs):
1326 1326 if s in toproceed:
1327 1327 # non final node with unknown closure
1328 1328 # We can't process this now
1329 1329 break
1330 1330 elif s in final:
1331 1331 # non final node, replace with closure
1332 1332 succs.remove(s)
1333 1333 succs.update(final[s])
1334 1334 else:
1335 1335 final[x] = succs
1336 1336 toproceed.remove(x)
1337 1337 # remove tmpnodes from final mapping
1338 1338 for n in tmpnodes:
1339 1339 del final[n]
1340 1340 # we expect all changes involved in final to exist in the repo
1341 1341 # turn `final` into list (topologically sorted)
1342 1342 nm = state.repo.changelog.nodemap
1343 1343 for prec, succs in final.items():
1344 1344 final[prec] = sorted(succs, key=nm.get)
1345 1345
1346 1346 # computed topmost element (necessary for bookmark)
1347 1347 if new:
1348 1348 newtopmost = sorted(new, key=state.repo.changelog.rev)[-1]
1349 1349 elif not final:
1350 1350 # Nothing rewritten at all. we won't need `newtopmost`
1351 1351 # It is the same as `oldtopmost` and `processreplacement` know it
1352 1352 newtopmost = None
1353 1353 else:
1354 1354 # every body died. The newtopmost is the parent of the root.
1355 1355 r = state.repo.changelog.rev
1356 1356 newtopmost = state.repo[sorted(final, key=r)[0]].p1().node()
1357 1357
1358 1358 return final, tmpnodes, new, newtopmost
1359 1359
1360 1360 def movebookmarks(ui, repo, mapping, oldtopmost, newtopmost):
1361 1361 """Move bookmark from old to newly created node"""
1362 1362 if not mapping:
1363 1363 # if nothing got rewritten there is not purpose for this function
1364 1364 return
1365 1365 moves = []
1366 1366 for bk, old in sorted(repo._bookmarks.iteritems()):
1367 1367 if old == oldtopmost:
1368 1368 # special case ensure bookmark stay on tip.
1369 1369 #
1370 1370 # This is arguably a feature and we may only want that for the
1371 1371 # active bookmark. But the behavior is kept compatible with the old
1372 1372 # version for now.
1373 1373 moves.append((bk, newtopmost))
1374 1374 continue
1375 1375 base = old
1376 1376 new = mapping.get(base, None)
1377 1377 if new is None:
1378 1378 continue
1379 1379 while not new:
1380 1380 # base is killed, trying with parent
1381 1381 base = repo[base].p1().node()
1382 1382 new = mapping.get(base, (base,))
1383 1383 # nothing to move
1384 1384 moves.append((bk, new[-1]))
1385 1385 if moves:
1386 1386 lock = tr = None
1387 1387 try:
1388 1388 lock = repo.lock()
1389 1389 tr = repo.transaction('histedit')
1390 1390 marks = repo._bookmarks
1391 1391 for mark, new in moves:
1392 1392 old = marks[mark]
1393 1393 ui.note(_('histedit: moving bookmarks %s from %s to %s\n')
1394 1394 % (mark, node.short(old), node.short(new)))
1395 1395 marks[mark] = new
1396 1396 marks.recordchange(tr)
1397 1397 tr.close()
1398 1398 finally:
1399 1399 release(tr, lock)
1400 1400
1401 1401 def cleanupnode(ui, repo, name, nodes):
1402 1402 """strip a group of nodes from the repository
1403 1403
1404 1404 The set of node to strip may contains unknown nodes."""
1405 1405 ui.debug('should strip %s nodes %s\n' %
1406 1406 (name, ', '.join([node.short(n) for n in nodes])))
1407 1407 lock = None
1408 1408 try:
1409 1409 lock = repo.lock()
1410 1410 # do not let filtering get in the way of the cleanse
1411 1411 # we should probably get rid of obsolescence marker created during the
1412 1412 # histedit, but we currently do not have such information.
1413 1413 repo = repo.unfiltered()
1414 1414 # Find all nodes that need to be stripped
1415 1415 # (we use %lr instead of %ln to silently ignore unknown items)
1416 1416 nm = repo.changelog.nodemap
1417 1417 nodes = sorted(n for n in nodes if n in nm)
1418 1418 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
1419 1419 for c in roots:
1420 1420 # We should process node in reverse order to strip tip most first.
1421 1421 # but this trigger a bug in changegroup hook.
1422 1422 # This would reduce bundle overhead
1423 1423 repair.strip(ui, repo, c)
1424 1424 finally:
1425 1425 release(lock)
1426 1426
1427 1427 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
1428 1428 if isinstance(nodelist, str):
1429 1429 nodelist = [nodelist]
1430 1430 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
1431 1431 state = histeditstate(repo)
1432 1432 state.read()
1433 1433 histedit_nodes = set([action.nodetoverify() for action
1434 1434 in state.actions if action.nodetoverify()])
1435 1435 strip_nodes = set([repo[n].node() for n in nodelist])
1436 1436 common_nodes = histedit_nodes & strip_nodes
1437 1437 if common_nodes:
1438 1438 raise error.Abort(_("histedit in progress, can't strip %s")
1439 1439 % ', '.join(node.short(x) for x in common_nodes))
1440 1440 return orig(ui, repo, nodelist, *args, **kwargs)
1441 1441
1442 1442 extensions.wrapfunction(repair, 'strip', stripwrapper)
1443 1443
1444 1444 def summaryhook(ui, repo):
1445 1445 if not os.path.exists(repo.join('histedit-state')):
1446 1446 return
1447 1447 state = histeditstate(repo)
1448 1448 state.read()
1449 1449 if state.actions:
1450 1450 # i18n: column positioning for "hg summary"
1451 1451 ui.write(_('hist: %s (histedit --continue)\n') %
1452 1452 (ui.label(_('%d remaining'), 'histedit.remaining') %
1453 1453 len(state.actions)))
1454 1454
1455 1455 def extsetup(ui):
1456 1456 cmdutil.summaryhooks.add('histedit', summaryhook)
1457 1457 cmdutil.unfinishedstates.append(
1458 1458 ['histedit-state', False, True, _('histedit in progress'),
1459 1459 _("use 'hg histedit --continue' or 'hg histedit --abort'")])
1460 cmdutil.afterresolvedstates.append(
1461 ['histedit-state', _('hg histedit --continue')])
1460 1462 if ui.configbool("experimental", "histeditng"):
1461 1463 globals()['base'] = addhisteditaction(['base', 'b'])(base)
@@ -1,261 +1,262 b''
1 1 $ . "$TESTDIR/histedit-helpers.sh"
2 2
3 3 $ cat >> $HGRCPATH <<EOF
4 4 > [alias]
5 5 > tglog = log -G --template "{rev}:{node}:{phase} '{desc}'\n"
6 6 > [extensions]
7 7 > histedit=
8 8 > [experimental]
9 9 > histeditng=True
10 10 > EOF
11 11
12 12 Create repo a:
13 13
14 14 $ hg init a
15 15 $ cd a
16 16 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
17 17 adding changesets
18 18 adding manifests
19 19 adding file changes
20 20 added 8 changesets with 7 changes to 7 files (+2 heads)
21 21 (run 'hg heads' to see heads, 'hg merge' to merge)
22 22 $ hg up tip
23 23 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
24 24
25 25 $ hg tglog
26 26 @ 7:02de42196ebee42ef284b6780a87cdc96e8eaab6:draft 'H'
27 27 |
28 28 | o 6:eea13746799a9e0bfd88f29d3c2e9dc9389f524f:draft 'G'
29 29 |/|
30 30 o | 5:24b6387c8c8cae37178880f3fa95ded3cb1cf785:draft 'F'
31 31 | |
32 32 | o 4:9520eea781bcca16c1e15acc0ba14335a0e8e5ba:draft 'E'
33 33 |/
34 34 | o 3:32af7686d403cf45b5d95f2d70cebea587ac806a:draft 'D'
35 35 | |
36 36 | o 2:5fddd98957c8a54a4d436dfe1da9d87f21a1b97b:draft 'C'
37 37 | |
38 38 | o 1:42ccdea3bb16d28e1848c95fe2e44c000f3f21b1:draft 'B'
39 39 |/
40 40 o 0:cd010b8cd998f3981a5a8115f94f8da4ab506089:draft 'A'
41 41
42 42
43 43
44 44 Go to D
45 45 $ hg update 3
46 46 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
47 47 edit the history to rebase B onto H
48 48
49 49
50 50 Rebase B onto H
51 51 $ hg histedit 1 --commands - 2>&1 << EOF | fixbundle
52 52 > base 02de42196ebe
53 53 > pick 42ccdea3bb16 B
54 54 > pick 5fddd98957c8 C
55 55 > pick 32af7686d403 D
56 56 > EOF
57 57
58 58 $ hg tglog
59 59 @ 7:0937e82309df47d14176ee15e45dbec5fbdef340:draft 'D'
60 60 |
61 61 o 6:f778d1cbddac4ab679d9983c9bb92e4c5e09e7fa:draft 'C'
62 62 |
63 63 o 5:3d41b7cc708545206213a842f96d812d2e73d818:draft 'B'
64 64 |
65 65 o 4:02de42196ebee42ef284b6780a87cdc96e8eaab6:draft 'H'
66 66 |
67 67 | o 3:eea13746799a9e0bfd88f29d3c2e9dc9389f524f:draft 'G'
68 68 |/|
69 69 o | 2:24b6387c8c8cae37178880f3fa95ded3cb1cf785:draft 'F'
70 70 | |
71 71 | o 1:9520eea781bcca16c1e15acc0ba14335a0e8e5ba:draft 'E'
72 72 |/
73 73 o 0:cd010b8cd998f3981a5a8115f94f8da4ab506089:draft 'A'
74 74
75 75 Rebase back and drop something
76 76 $ hg histedit 5 --commands - 2>&1 << EOF | fixbundle
77 77 > base cd010b8cd998
78 78 > pick 3d41b7cc7085 B
79 79 > drop f778d1cbddac C
80 80 > pick 0937e82309df D
81 81 > EOF
82 82
83 83 $ hg tglog
84 84 @ 6:476cc3e4168da2d036b141f7f7dcff7f8e3fe846:draft 'D'
85 85 |
86 86 o 5:d273e35dcdf21a7eb305192ef2e362887cd0a6f8:draft 'B'
87 87 |
88 88 | o 4:02de42196ebee42ef284b6780a87cdc96e8eaab6:draft 'H'
89 89 | |
90 90 | | o 3:eea13746799a9e0bfd88f29d3c2e9dc9389f524f:draft 'G'
91 91 | |/|
92 92 | o | 2:24b6387c8c8cae37178880f3fa95ded3cb1cf785:draft 'F'
93 93 |/ /
94 94 | o 1:9520eea781bcca16c1e15acc0ba14335a0e8e5ba:draft 'E'
95 95 |/
96 96 o 0:cd010b8cd998f3981a5a8115f94f8da4ab506089:draft 'A'
97 97
98 98 Split stack
99 99 $ hg histedit 5 --commands - 2>&1 << EOF | fixbundle
100 100 > base cd010b8cd998
101 101 > pick d273e35dcdf2 B
102 102 > base cd010b8cd998
103 103 > pick 476cc3e4168d D
104 104 > EOF
105 105
106 106 $ hg tglog
107 107 @ 6:d7a6f907a822c4ce6f15662ae45a42aa46d3818a:draft 'D'
108 108 |
109 109 | o 5:d273e35dcdf21a7eb305192ef2e362887cd0a6f8:draft 'B'
110 110 |/
111 111 | o 4:02de42196ebee42ef284b6780a87cdc96e8eaab6:draft 'H'
112 112 | |
113 113 | | o 3:eea13746799a9e0bfd88f29d3c2e9dc9389f524f:draft 'G'
114 114 | |/|
115 115 | o | 2:24b6387c8c8cae37178880f3fa95ded3cb1cf785:draft 'F'
116 116 |/ /
117 117 | o 1:9520eea781bcca16c1e15acc0ba14335a0e8e5ba:draft 'E'
118 118 |/
119 119 o 0:cd010b8cd998f3981a5a8115f94f8da4ab506089:draft 'A'
120 120
121 121 Abort
122 122 $ echo x > B
123 123 $ hg add B
124 124 $ hg commit -m "X"
125 125 $ hg tglog
126 126 @ 7:591369deedfdcbf57471e894999a70d7f676186d:draft 'X'
127 127 |
128 128 o 6:d7a6f907a822c4ce6f15662ae45a42aa46d3818a:draft 'D'
129 129 |
130 130 | o 5:d273e35dcdf21a7eb305192ef2e362887cd0a6f8:draft 'B'
131 131 |/
132 132 | o 4:02de42196ebee42ef284b6780a87cdc96e8eaab6:draft 'H'
133 133 | |
134 134 | | o 3:eea13746799a9e0bfd88f29d3c2e9dc9389f524f:draft 'G'
135 135 | |/|
136 136 | o | 2:24b6387c8c8cae37178880f3fa95ded3cb1cf785:draft 'F'
137 137 |/ /
138 138 | o 1:9520eea781bcca16c1e15acc0ba14335a0e8e5ba:draft 'E'
139 139 |/
140 140 o 0:cd010b8cd998f3981a5a8115f94f8da4ab506089:draft 'A'
141 141
142 142 $ hg histedit 6 --commands - 2>&1 << EOF | fixbundle
143 143 > base d273e35dcdf2 B
144 144 > drop d7a6f907a822 D
145 145 > pick 591369deedfd X
146 146 > EOF
147 147 merging B
148 148 warning: conflicts while merging B! (edit, then use 'hg resolve --mark')
149 149 Fix up the change and run hg histedit --continue
150 150 $ hg histedit --abort | fixbundle
151 151 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
152 152 $ hg tglog
153 153 @ 7:591369deedfdcbf57471e894999a70d7f676186d:draft 'X'
154 154 |
155 155 o 6:d7a6f907a822c4ce6f15662ae45a42aa46d3818a:draft 'D'
156 156 |
157 157 | o 5:d273e35dcdf21a7eb305192ef2e362887cd0a6f8:draft 'B'
158 158 |/
159 159 | o 4:02de42196ebee42ef284b6780a87cdc96e8eaab6:draft 'H'
160 160 | |
161 161 | | o 3:eea13746799a9e0bfd88f29d3c2e9dc9389f524f:draft 'G'
162 162 | |/|
163 163 | o | 2:24b6387c8c8cae37178880f3fa95ded3cb1cf785:draft 'F'
164 164 |/ /
165 165 | o 1:9520eea781bcca16c1e15acc0ba14335a0e8e5ba:draft 'E'
166 166 |/
167 167 o 0:cd010b8cd998f3981a5a8115f94f8da4ab506089:draft 'A'
168 168
169 169 Continue
170 170 $ hg histedit 6 --commands - 2>&1 << EOF | fixbundle
171 171 > base d273e35dcdf2 B
172 172 > drop d7a6f907a822 D
173 173 > pick 591369deedfd X
174 174 > EOF
175 175 merging B
176 176 warning: conflicts while merging B! (edit, then use 'hg resolve --mark')
177 177 Fix up the change and run hg histedit --continue
178 178 $ echo b2 > B
179 179 $ hg resolve --mark B
180 180 (no more unresolved files)
181 continue: hg histedit --continue
181 182 $ hg histedit --continue | fixbundle
182 183 $ hg tglog
183 184 @ 6:03772da75548bb42a8f1eacd8c91d0717a147fcd:draft 'X'
184 185 |
185 186 o 5:d273e35dcdf21a7eb305192ef2e362887cd0a6f8:draft 'B'
186 187 |
187 188 | o 4:02de42196ebee42ef284b6780a87cdc96e8eaab6:draft 'H'
188 189 | |
189 190 | | o 3:eea13746799a9e0bfd88f29d3c2e9dc9389f524f:draft 'G'
190 191 | |/|
191 192 | o | 2:24b6387c8c8cae37178880f3fa95ded3cb1cf785:draft 'F'
192 193 |/ /
193 194 | o 1:9520eea781bcca16c1e15acc0ba14335a0e8e5ba:draft 'E'
194 195 |/
195 196 o 0:cd010b8cd998f3981a5a8115f94f8da4ab506089:draft 'A'
196 197
197 198
198 199 base on a previously picked changeset
199 200 $ echo i > i
200 201 $ hg add i
201 202 $ hg commit -m "I"
202 203 $ echo j > j
203 204 $ hg add j
204 205 $ hg commit -m "J"
205 206 $ hg tglog
206 207 @ 8:e8c55b19d366b335626e805484110d1d5f6f2ea3:draft 'J'
207 208 |
208 209 o 7:b2f90fd8aa85db5569e3cfc30cd1d7739546368e:draft 'I'
209 210 |
210 211 o 6:03772da75548bb42a8f1eacd8c91d0717a147fcd:draft 'X'
211 212 |
212 213 o 5:d273e35dcdf21a7eb305192ef2e362887cd0a6f8:draft 'B'
213 214 |
214 215 | o 4:02de42196ebee42ef284b6780a87cdc96e8eaab6:draft 'H'
215 216 | |
216 217 | | o 3:eea13746799a9e0bfd88f29d3c2e9dc9389f524f:draft 'G'
217 218 | |/|
218 219 | o | 2:24b6387c8c8cae37178880f3fa95ded3cb1cf785:draft 'F'
219 220 |/ /
220 221 | o 1:9520eea781bcca16c1e15acc0ba14335a0e8e5ba:draft 'E'
221 222 |/
222 223 o 0:cd010b8cd998f3981a5a8115f94f8da4ab506089:draft 'A'
223 224
224 225 $ hg histedit 5 --commands - 2>&1 << EOF | fixbundle
225 226 > pick d273e35dcdf2 B
226 227 > pick 03772da75548 X
227 228 > base d273e35dcdf2 B
228 229 > pick e8c55b19d366 J
229 230 > base d273e35dcdf2 B
230 231 > pick b2f90fd8aa85 I
231 232 > EOF
232 233 hg: parse error: may not use "base" with changesets within the edited list
233 234
234 235 $ hg --config experimental.histeditng=False histedit 5 --commands - 2>&1 << EOF | fixbundle
235 236 > base cd010b8cd998 A
236 237 > pick d273e35dcdf2 B
237 238 > pick 03772da75548 X
238 239 > pick b2f90fd8aa85 I
239 240 > pick e8c55b19d366 J
240 241 > EOF
241 242 hg: parse error: unknown action "base"
242 243
243 244 $ hg tglog
244 245 @ 8:e8c55b19d366b335626e805484110d1d5f6f2ea3:draft 'J'
245 246 |
246 247 o 7:b2f90fd8aa85db5569e3cfc30cd1d7739546368e:draft 'I'
247 248 |
248 249 o 6:03772da75548bb42a8f1eacd8c91d0717a147fcd:draft 'X'
249 250 |
250 251 o 5:d273e35dcdf21a7eb305192ef2e362887cd0a6f8:draft 'B'
251 252 |
252 253 | o 4:02de42196ebee42ef284b6780a87cdc96e8eaab6:draft 'H'
253 254 | |
254 255 | | o 3:eea13746799a9e0bfd88f29d3c2e9dc9389f524f:draft 'G'
255 256 | |/|
256 257 | o | 2:24b6387c8c8cae37178880f3fa95ded3cb1cf785:draft 'F'
257 258 |/ /
258 259 | o 1:9520eea781bcca16c1e15acc0ba14335a0e8e5ba:draft 'E'
259 260 |/
260 261 o 0:cd010b8cd998f3981a5a8115f94f8da4ab506089:draft 'A'
261 262
@@ -1,339 +1,343 b''
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 while merging e! (edit, then use 'hg resolve --mark')
92 92 Fix up the change and run hg histedit --continue
93 93
94 94 fix up
95 95 $ echo 'I can haz no commute' > e
96 96 $ hg resolve --mark e
97 97 (no more unresolved files)
98 continue: hg histedit --continue
98 99 $ cat > cat.py <<EOF
99 100 > import sys
100 101 > print open(sys.argv[1]).read()
101 102 > print
102 103 > print
103 104 > EOF
104 105 $ HGEDITOR="python cat.py" hg histedit --continue 2>&1 | fixbundle | grep -v '2 files removed'
105 106 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
106 107 d
107 108 ***
108 109 does not commute with e
109 110
110 111
111 112
112 113 HG: Enter commit message. Lines beginning with 'HG:' are removed.
113 114 HG: Leave message empty to abort commit.
114 115 HG: --
115 116 HG: user: test
116 117 HG: branch 'default'
117 118 HG: changed d
118 119 HG: changed e
119 120
120 121
121 122
122 123 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
123 124 merging e
124 125 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
125 126 Fix up the change and run hg histedit --continue
126 127
127 128 just continue this time
128 129 $ hg revert -r 'p1()' e
129 130 $ hg resolve --mark e
130 131 (no more unresolved files)
132 continue: hg histedit --continue
131 133 $ hg histedit --continue 2>&1 | fixbundle
132 134 7b4e2f4b7bcd: empty changeset
133 135
134 136 log after edit
135 137 $ hg log --graph
136 138 @ changeset: 5:d9cf42e54966
137 139 | tag: tip
138 140 | user: test
139 141 | date: Thu Jan 01 00:00:00 1970 +0000
140 142 | summary: f
141 143 |
142 144 o changeset: 4:10486af2e984
143 145 | user: test
144 146 | date: Thu Jan 01 00:00:00 1970 +0000
145 147 | summary: d
146 148 |
147 149 o changeset: 3:65a9a84f33fd
148 150 | user: test
149 151 | date: Thu Jan 01 00:00:00 1970 +0000
150 152 | summary: c
151 153 |
152 154 o changeset: 2:da6535b52e45
153 155 | user: test
154 156 | date: Thu Jan 01 00:00:00 1970 +0000
155 157 | summary: b
156 158 |
157 159 o changeset: 1:c1f09da44841
158 160 | user: test
159 161 | date: Thu Jan 01 00:00:00 1970 +0000
160 162 | summary: a
161 163 |
162 164 o changeset: 0:1715188a53c7
163 165 user: test
164 166 date: Thu Jan 01 00:00:00 1970 +0000
165 167 summary: Initial commit
166 168
167 169
168 170 contents of e
169 171 $ hg cat e
170 172 I can haz no commute
171 173
172 174 manifest
173 175 $ hg manifest
174 176 a
175 177 b
176 178 c
177 179 d
178 180 e
179 181 f
180 182
181 183 $ cd ..
182 184
183 185 Repeat test using "roll", not "fold". "roll" folds in changes but drops message
184 186
185 187 $ initrepo r2
186 188 $ cd r2
187 189
188 190 Initial generation of the command files
189 191
190 192 $ EDITED="$TESTTMP/editedhistory.2"
191 193 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 3 >> $EDITED
192 194 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 4 >> $EDITED
193 195 $ hg log --template 'roll {node|short} {rev} {desc}\n' -r 7 >> $EDITED
194 196 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 5 >> $EDITED
195 197 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 6 >> $EDITED
196 198 $ cat $EDITED
197 199 pick 65a9a84f33fd 3 c
198 200 pick 00f1c5383965 4 d
199 201 roll 39522b764e3d 7 does not commute with e
200 202 pick 7b4e2f4b7bcd 5 e
201 203 pick 500cac37a696 6 f
202 204
203 205 log before edit
204 206 $ hg log --graph
205 207 @ changeset: 7:39522b764e3d
206 208 | tag: tip
207 209 | user: test
208 210 | date: Thu Jan 01 00:00:00 1970 +0000
209 211 | summary: does not commute with e
210 212 |
211 213 o changeset: 6:500cac37a696
212 214 | user: test
213 215 | date: Thu Jan 01 00:00:00 1970 +0000
214 216 | summary: f
215 217 |
216 218 o changeset: 5:7b4e2f4b7bcd
217 219 | user: test
218 220 | date: Thu Jan 01 00:00:00 1970 +0000
219 221 | summary: e
220 222 |
221 223 o changeset: 4:00f1c5383965
222 224 | user: test
223 225 | date: Thu Jan 01 00:00:00 1970 +0000
224 226 | summary: d
225 227 |
226 228 o changeset: 3:65a9a84f33fd
227 229 | user: test
228 230 | date: Thu Jan 01 00:00:00 1970 +0000
229 231 | summary: c
230 232 |
231 233 o changeset: 2:da6535b52e45
232 234 | user: test
233 235 | date: Thu Jan 01 00:00:00 1970 +0000
234 236 | summary: b
235 237 |
236 238 o changeset: 1:c1f09da44841
237 239 | user: test
238 240 | date: Thu Jan 01 00:00:00 1970 +0000
239 241 | summary: a
240 242 |
241 243 o changeset: 0:1715188a53c7
242 244 user: test
243 245 date: Thu Jan 01 00:00:00 1970 +0000
244 246 summary: Initial commit
245 247
246 248
247 249 edit the history
248 250 $ hg histedit 3 --commands $EDITED 2>&1 | fixbundle
249 251 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
250 252 merging e
251 253 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
252 254 Fix up the change and run hg histedit --continue
253 255
254 256 fix up
255 257 $ echo 'I can haz no commute' > e
256 258 $ hg resolve --mark e
257 259 (no more unresolved files)
260 continue: hg histedit --continue
258 261 $ hg histedit --continue 2>&1 | fixbundle | grep -v '2 files removed'
259 262 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
260 263 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
261 264 merging e
262 265 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
263 266 Fix up the change and run hg histedit --continue
264 267
265 268 just continue this time
266 269 $ hg revert -r 'p1()' e
267 270 $ hg resolve --mark e
268 271 (no more unresolved files)
272 continue: hg histedit --continue
269 273 $ hg histedit --continue 2>&1 | fixbundle
270 274 7b4e2f4b7bcd: empty changeset
271 275
272 276 log after edit
273 277 $ hg log --graph
274 278 @ changeset: 5:e7c4f5d4eb75
275 279 | tag: tip
276 280 | user: test
277 281 | date: Thu Jan 01 00:00:00 1970 +0000
278 282 | summary: f
279 283 |
280 284 o changeset: 4:803d1bb561fc
281 285 | user: test
282 286 | date: Thu Jan 01 00:00:00 1970 +0000
283 287 | summary: d
284 288 |
285 289 o changeset: 3:65a9a84f33fd
286 290 | user: test
287 291 | date: Thu Jan 01 00:00:00 1970 +0000
288 292 | summary: c
289 293 |
290 294 o changeset: 2:da6535b52e45
291 295 | user: test
292 296 | date: Thu Jan 01 00:00:00 1970 +0000
293 297 | summary: b
294 298 |
295 299 o changeset: 1:c1f09da44841
296 300 | user: test
297 301 | date: Thu Jan 01 00:00:00 1970 +0000
298 302 | summary: a
299 303 |
300 304 o changeset: 0:1715188a53c7
301 305 user: test
302 306 date: Thu Jan 01 00:00:00 1970 +0000
303 307 summary: Initial commit
304 308
305 309
306 310 contents of e
307 311 $ hg cat e
308 312 I can haz no commute
309 313
310 314 manifest
311 315 $ hg manifest
312 316 a
313 317 b
314 318 c
315 319 d
316 320 e
317 321 f
318 322
319 323 description is taken from rollup target commit
320 324
321 325 $ hg log --debug --rev 4
322 326 changeset: 4:803d1bb561fceac3129ec778db9da249a3106fc3
323 327 phase: draft
324 328 parent: 3:65a9a84f33fdeb1ad5679b3941ec885d2b24027b
325 329 parent: -1:0000000000000000000000000000000000000000
326 330 manifest: 4:b068a323d969f22af1296ec6a5ea9384cef437ac
327 331 user: test
328 332 date: Thu Jan 01 00:00:00 1970 +0000
329 333 files: d e
330 334 extra: branch=default
331 335 extra: histedit_source=00f1c53839651fa5c76d423606811ea5455a79d0,39522b764e3d26103f08bd1fa2ccd3e3d7dbcf4e
332 336 description:
333 337 d
334 338
335 339
336 340
337 341 done with repo r2
338 342
339 343 $ cd ..
@@ -1,559 +1,561 b''
1 1 Test histedit extension: Fold commands
2 2 ======================================
3 3
4 4 This test file is dedicated to testing the fold command in non conflicting
5 5 case.
6 6
7 7 Initialization
8 8 ---------------
9 9
10 10
11 11 $ . "$TESTDIR/histedit-helpers.sh"
12 12
13 13 $ cat >> $HGRCPATH <<EOF
14 14 > [alias]
15 15 > logt = log --template '{rev}:{node|short} {desc|firstline}\n'
16 16 > [extensions]
17 17 > histedit=
18 18 > EOF
19 19
20 20
21 21 Simple folding
22 22 --------------------
23 23 $ initrepo ()
24 24 > {
25 25 > hg init r
26 26 > cd r
27 27 > for x in a b c d e f ; do
28 28 > echo $x > $x
29 29 > hg add $x
30 30 > hg ci -m $x
31 31 > done
32 32 > }
33 33
34 34 $ initrepo
35 35
36 36 log before edit
37 37 $ hg logt --graph
38 38 @ 5:652413bf663e f
39 39 |
40 40 o 4:e860deea161a e
41 41 |
42 42 o 3:055a42cdd887 d
43 43 |
44 44 o 2:177f92b77385 c
45 45 |
46 46 o 1:d2ae7f538514 b
47 47 |
48 48 o 0:cb9a9f314b8b a
49 49
50 50
51 51 $ hg histedit 177f92b77385 --commands - 2>&1 <<EOF | fixbundle
52 52 > pick e860deea161a e
53 53 > pick 652413bf663e f
54 54 > fold 177f92b77385 c
55 55 > pick 055a42cdd887 d
56 56 > EOF
57 57 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
58 58 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
59 59 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
60 60
61 61 log after edit
62 62 $ hg logt --graph
63 63 @ 4:9c277da72c9b d
64 64 |
65 65 o 3:6de59d13424a f
66 66 |
67 67 o 2:ee283cb5f2d5 e
68 68 |
69 69 o 1:d2ae7f538514 b
70 70 |
71 71 o 0:cb9a9f314b8b a
72 72
73 73
74 74 post-fold manifest
75 75 $ hg manifest
76 76 a
77 77 b
78 78 c
79 79 d
80 80 e
81 81 f
82 82
83 83
84 84 check histedit_source
85 85
86 86 $ hg log --debug --rev 3
87 87 changeset: 3:6de59d13424a8a13acd3e975514aed29dd0d9b2d
88 88 phase: draft
89 89 parent: 2:ee283cb5f2d5955443f23a27b697a04339e9a39a
90 90 parent: -1:0000000000000000000000000000000000000000
91 91 manifest: 3:81eede616954057198ead0b2c73b41d1f392829a
92 92 user: test
93 93 date: Thu Jan 01 00:00:00 1970 +0000
94 94 files+: c f
95 95 extra: branch=default
96 96 extra: histedit_source=a4f7421b80f79fcc59fff01bcbf4a53d127dd6d3,177f92b773850b59254aa5e923436f921b55483b
97 97 description:
98 98 f
99 99 ***
100 100 c
101 101
102 102
103 103
104 104 rollup will fold without preserving the folded commit's message
105 105
106 106 $ OLDHGEDITOR=$HGEDITOR
107 107 $ HGEDITOR=false
108 108 $ hg histedit d2ae7f538514 --commands - 2>&1 <<EOF | fixbundle
109 109 > pick d2ae7f538514 b
110 110 > roll ee283cb5f2d5 e
111 111 > pick 6de59d13424a f
112 112 > pick 9c277da72c9b d
113 113 > EOF
114 114 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
115 115 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
116 116 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
117 117
118 118 $ HGEDITOR=$OLDHGEDITOR
119 119
120 120 log after edit
121 121 $ hg logt --graph
122 122 @ 3:c4a9eb7989fc d
123 123 |
124 124 o 2:8e03a72b6f83 f
125 125 |
126 126 o 1:391ee782c689 b
127 127 |
128 128 o 0:cb9a9f314b8b a
129 129
130 130
131 131 description is taken from rollup target commit
132 132
133 133 $ hg log --debug --rev 1
134 134 changeset: 1:391ee782c68930be438ccf4c6a403daedbfbffa5
135 135 phase: draft
136 136 parent: 0:cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
137 137 parent: -1:0000000000000000000000000000000000000000
138 138 manifest: 1:b5e112a3a8354e269b1524729f0918662d847c38
139 139 user: test
140 140 date: Thu Jan 01 00:00:00 1970 +0000
141 141 files+: b e
142 142 extra: branch=default
143 143 extra: histedit_source=d2ae7f538514cd87c17547b0de4cea71fe1af9fb,ee283cb5f2d5955443f23a27b697a04339e9a39a
144 144 description:
145 145 b
146 146
147 147
148 148
149 149 check saving last-message.txt
150 150
151 151 $ cat > $TESTTMP/abortfolding.py <<EOF
152 152 > from mercurial import util
153 153 > def abortfolding(ui, repo, hooktype, **kwargs):
154 154 > ctx = repo[kwargs.get('node')]
155 155 > if set(ctx.files()) == set(['c', 'd', 'f']):
156 156 > return True # abort folding commit only
157 157 > ui.warn('allow non-folding commit\\n')
158 158 > EOF
159 159 $ cat > .hg/hgrc <<EOF
160 160 > [hooks]
161 161 > pretxncommit.abortfolding = python:$TESTTMP/abortfolding.py:abortfolding
162 162 > EOF
163 163
164 164 $ cat > $TESTTMP/editor.sh << EOF
165 165 > echo "==== before editing"
166 166 > cat \$1
167 167 > echo "===="
168 168 > echo "check saving last-message.txt" >> \$1
169 169 > EOF
170 170
171 171 $ rm -f .hg/last-message.txt
172 172 $ hg status --rev '8e03a72b6f83^1::c4a9eb7989fc'
173 173 A c
174 174 A d
175 175 A f
176 176 $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit 8e03a72b6f83 --commands - 2>&1 <<EOF
177 177 > pick 8e03a72b6f83 f
178 178 > fold c4a9eb7989fc d
179 179 > EOF
180 180 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
181 181 adding d
182 182 allow non-folding commit
183 183 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
184 184 ==== before editing
185 185 f
186 186 ***
187 187 c
188 188 ***
189 189 d
190 190
191 191
192 192
193 193 HG: Enter commit message. Lines beginning with 'HG:' are removed.
194 194 HG: Leave message empty to abort commit.
195 195 HG: --
196 196 HG: user: test
197 197 HG: branch 'default'
198 198 HG: added c
199 199 HG: added d
200 200 HG: added f
201 201 ====
202 202 transaction abort!
203 203 rollback completed
204 204 abort: pretxncommit.abortfolding hook failed
205 205 [255]
206 206
207 207 $ cat .hg/last-message.txt
208 208 f
209 209 ***
210 210 c
211 211 ***
212 212 d
213 213
214 214
215 215
216 216 check saving last-message.txt
217 217
218 218 $ cd ..
219 219 $ rm -r r
220 220
221 221 folding preserves initial author
222 222 --------------------------------
223 223
224 224 $ initrepo
225 225
226 226 $ hg ci --user "someone else" --amend --quiet
227 227
228 228 tip before edit
229 229 $ hg log --rev .
230 230 changeset: 5:a00ad806cb55
231 231 tag: tip
232 232 user: someone else
233 233 date: Thu Jan 01 00:00:00 1970 +0000
234 234 summary: f
235 235
236 236
237 237 $ hg --config progress.debug=1 --debug \
238 238 > histedit e860deea161a --commands - 2>&1 <<EOF | \
239 239 > egrep 'editing|unresolved'
240 240 > pick e860deea161a e
241 241 > fold a00ad806cb55 f
242 242 > EOF
243 243 editing: pick e860deea161a 4 e 1/2 changes (50.00%)
244 244 editing: fold a00ad806cb55 5 f 2/2 changes (100.00%)
245 245 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
246 246 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
247 247 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
248 248
249 249 tip after edit
250 250 $ hg log --rev .
251 251 changeset: 4:698d4e8040a1
252 252 tag: tip
253 253 user: test
254 254 date: Thu Jan 01 00:00:00 1970 +0000
255 255 summary: e
256 256
257 257
258 258 $ cd ..
259 259 $ rm -r r
260 260
261 261 folding and creating no new change doesn't break:
262 262 -------------------------------------------------
263 263
264 264 folded content is dropped during a merge. The folded commit should properly disappear.
265 265
266 266 $ mkdir fold-to-empty-test
267 267 $ cd fold-to-empty-test
268 268 $ hg init
269 269 $ printf "1\n2\n3\n" > file
270 270 $ hg add file
271 271 $ hg commit -m '1+2+3'
272 272 $ echo 4 >> file
273 273 $ hg commit -m '+4'
274 274 $ echo 5 >> file
275 275 $ hg commit -m '+5'
276 276 $ echo 6 >> file
277 277 $ hg commit -m '+6'
278 278 $ hg logt --graph
279 279 @ 3:251d831eeec5 +6
280 280 |
281 281 o 2:888f9082bf99 +5
282 282 |
283 283 o 1:617f94f13c0f +4
284 284 |
285 285 o 0:0189ba417d34 1+2+3
286 286
287 287
288 288 $ hg histedit 1 --commands - << EOF
289 289 > pick 617f94f13c0f 1 +4
290 290 > drop 888f9082bf99 2 +5
291 291 > fold 251d831eeec5 3 +6
292 292 > EOF
293 293 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
294 294 merging file
295 295 warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
296 296 Fix up the change and run hg histedit --continue
297 297 [1]
298 298 There were conflicts, we keep P1 content. This
299 299 should effectively drop the changes from +6.
300 300 $ hg status
301 301 M file
302 302 ? file.orig
303 303 $ hg resolve -l
304 304 U file
305 305 $ hg revert -r 'p1()' file
306 306 $ hg resolve --mark file
307 307 (no more unresolved files)
308 continue: hg histedit --continue
308 309 $ hg histedit --continue
309 310 251d831eeec5: empty changeset
310 311 saved backup bundle to $TESTTMP/*-backup.hg (glob)
311 312 $ hg logt --graph
312 313 @ 1:617f94f13c0f +4
313 314 |
314 315 o 0:0189ba417d34 1+2+3
315 316
316 317
317 318 $ cd ..
318 319
319 320
320 321 Test fold through dropped
321 322 -------------------------
322 323
323 324
324 325 Test corner case where folded revision is separated from its parent by a
325 326 dropped revision.
326 327
327 328
328 329 $ hg init fold-with-dropped
329 330 $ cd fold-with-dropped
330 331 $ printf "1\n2\n3\n" > file
331 332 $ hg commit -Am '1+2+3'
332 333 adding file
333 334 $ echo 4 >> file
334 335 $ hg commit -m '+4'
335 336 $ echo 5 >> file
336 337 $ hg commit -m '+5'
337 338 $ echo 6 >> file
338 339 $ hg commit -m '+6'
339 340 $ hg logt -G
340 341 @ 3:251d831eeec5 +6
341 342 |
342 343 o 2:888f9082bf99 +5
343 344 |
344 345 o 1:617f94f13c0f +4
345 346 |
346 347 o 0:0189ba417d34 1+2+3
347 348
348 349 $ hg histedit 1 --commands - << EOF
349 350 > pick 617f94f13c0f 1 +4
350 351 > drop 888f9082bf99 2 +5
351 352 > fold 251d831eeec5 3 +6
352 353 > EOF
353 354 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
354 355 merging file
355 356 warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
356 357 Fix up the change and run hg histedit --continue
357 358 [1]
358 359 $ cat > file << EOF
359 360 > 1
360 361 > 2
361 362 > 3
362 363 > 4
363 364 > 5
364 365 > EOF
365 366 $ hg resolve --mark file
366 367 (no more unresolved files)
368 continue: hg histedit --continue
367 369 $ hg commit -m '+5.2'
368 370 created new head
369 371 $ echo 6 >> file
370 372 $ HGEDITOR=cat hg histedit --continue
371 373 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
372 374 +4
373 375 ***
374 376 +5.2
375 377 ***
376 378 +6
377 379
378 380
379 381
380 382 HG: Enter commit message. Lines beginning with 'HG:' are removed.
381 383 HG: Leave message empty to abort commit.
382 384 HG: --
383 385 HG: user: test
384 386 HG: branch 'default'
385 387 HG: changed file
386 388 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
387 389 saved backup bundle to $TESTTMP/fold-with-dropped/.hg/strip-backup/55c8d8dc79ce-4066cd98-backup.hg (glob)
388 390 saved backup bundle to $TESTTMP/fold-with-dropped/.hg/strip-backup/617f94f13c0f-a35700fc-backup.hg (glob)
389 391 $ hg logt -G
390 392 @ 1:10c647b2cdd5 +4
391 393 |
392 394 o 0:0189ba417d34 1+2+3
393 395
394 396 $ hg export tip
395 397 # HG changeset patch
396 398 # User test
397 399 # Date 0 0
398 400 # Thu Jan 01 00:00:00 1970 +0000
399 401 # Node ID 10c647b2cdd54db0603ecb99b2ff5ce66d5a5323
400 402 # Parent 0189ba417d34df9dda55f88b637dcae9917b5964
401 403 +4
402 404 ***
403 405 +5.2
404 406 ***
405 407 +6
406 408
407 409 diff -r 0189ba417d34 -r 10c647b2cdd5 file
408 410 --- a/file Thu Jan 01 00:00:00 1970 +0000
409 411 +++ b/file Thu Jan 01 00:00:00 1970 +0000
410 412 @@ -1,3 +1,6 @@
411 413 1
412 414 2
413 415 3
414 416 +4
415 417 +5
416 418 +6
417 419 $ cd ..
418 420
419 421
420 422 Folding with initial rename (issue3729)
421 423 ---------------------------------------
422 424
423 425 $ hg init fold-rename
424 426 $ cd fold-rename
425 427 $ echo a > a.txt
426 428 $ hg add a.txt
427 429 $ hg commit -m a
428 430 $ hg rename a.txt b.txt
429 431 $ hg commit -m rename
430 432 $ echo b >> b.txt
431 433 $ hg commit -m b
432 434
433 435 $ hg logt --follow b.txt
434 436 2:e0371e0426bc b
435 437 1:1c4f440a8085 rename
436 438 0:6c795aa153cb a
437 439
438 440 $ hg histedit 1c4f440a8085 --commands - 2>&1 << EOF | fixbundle
439 441 > pick 1c4f440a8085 rename
440 442 > fold e0371e0426bc b
441 443 > EOF
442 444 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
443 445 reverting b.txt
444 446 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
445 447 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
446 448
447 449 $ hg logt --follow b.txt
448 450 1:cf858d235c76 rename
449 451 0:6c795aa153cb a
450 452
451 453 $ cd ..
452 454
453 455 Folding with swapping
454 456 ---------------------
455 457
456 458 This is an excuse to test hook with histedit temporary commit (issue4422)
457 459
458 460
459 461 $ hg init issue4422
460 462 $ cd issue4422
461 463 $ echo a > a.txt
462 464 $ hg add a.txt
463 465 $ hg commit -m a
464 466 $ echo b > b.txt
465 467 $ hg add b.txt
466 468 $ hg commit -m b
467 469 $ echo c > c.txt
468 470 $ hg add c.txt
469 471 $ hg commit -m c
470 472
471 473 $ hg logt
472 474 2:a1a953ffb4b0 c
473 475 1:199b6bb90248 b
474 476 0:6c795aa153cb a
475 477
476 478 Setup the proper environment variable symbol for the platform, to be subbed
477 479 into the hook command.
478 480 #if windows
479 481 $ NODE="%HG_NODE%"
480 482 #else
481 483 $ NODE="\$HG_NODE"
482 484 #endif
483 485 $ hg histedit 6c795aa153cb --config hooks.commit="echo commit $NODE" --commands - 2>&1 << EOF | fixbundle
484 486 > pick 199b6bb90248 b
485 487 > fold a1a953ffb4b0 c
486 488 > pick 6c795aa153cb a
487 489 > EOF
488 490 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
489 491 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
490 492 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
491 493 commit 9599899f62c05f4377548c32bf1c9f1a39634b0c
492 494
493 495 $ hg logt
494 496 1:9599899f62c0 a
495 497 0:79b99e9c8e49 b
496 498
497 499 $ echo "foo" > amended.txt
498 500 $ hg add amended.txt
499 501 $ hg ci -q --config extensions.largefiles= --amend -I amended.txt
500 502
501 503 Test that folding multiple changes in a row doesn't show multiple
502 504 editors.
503 505
504 506 $ echo foo >> foo
505 507 $ hg add foo
506 508 $ hg ci -m foo1
507 509 $ echo foo >> foo
508 510 $ hg ci -m foo2
509 511 $ echo foo >> foo
510 512 $ hg ci -m foo3
511 513 $ hg logt
512 514 4:21679ff7675c foo3
513 515 3:b7389cc4d66e foo2
514 516 2:0e01aeef5fa8 foo1
515 517 1:578c7455730c a
516 518 0:79b99e9c8e49 b
517 519 $ cat > "$TESTTMP/editor.sh" <<EOF
518 520 > echo ran editor >> "$TESTTMP/editorlog.txt"
519 521 > cat \$1 >> "$TESTTMP/editorlog.txt"
520 522 > echo END >> "$TESTTMP/editorlog.txt"
521 523 > echo merged foos > \$1
522 524 > EOF
523 525 $ HGEDITOR="sh \"$TESTTMP/editor.sh\"" hg histedit 1 --commands - 2>&1 <<EOF | fixbundle
524 526 > pick 578c7455730c 1 a
525 527 > pick 0e01aeef5fa8 2 foo1
526 528 > fold b7389cc4d66e 3 foo2
527 529 > fold 21679ff7675c 4 foo3
528 530 > EOF
529 531 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
530 532 reverting foo
531 533 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
532 534 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
533 535 merging foo
534 536 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
535 537 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
536 538 $ hg logt
537 539 2:e8bedbda72c1 merged foos
538 540 1:578c7455730c a
539 541 0:79b99e9c8e49 b
540 542 Editor should have run only once
541 543 $ cat $TESTTMP/editorlog.txt
542 544 ran editor
543 545 foo1
544 546 ***
545 547 foo2
546 548 ***
547 549 foo3
548 550
549 551
550 552
551 553 HG: Enter commit message. Lines beginning with 'HG:' are removed.
552 554 HG: Leave message empty to abort commit.
553 555 HG: --
554 556 HG: user: test
555 557 HG: branch 'default'
556 558 HG: added foo
557 559 END
558 560
559 561 $ cd ..
@@ -1,289 +1,293 b''
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 while merging e! (edit, then use 'hg resolve --mark')
93 93 Fix up the change and run hg histedit --continue
94 94
95 95 abort the edit
96 96 $ hg histedit --abort 2>&1 | fixbundle
97 97 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
98 98
99 99
100 100 second edit set
101 101
102 102 $ hg log --graph
103 103 @ changeset: 7:39522b764e3d
104 104 | tag: tip
105 105 | user: test
106 106 | date: Thu Jan 01 00:00:00 1970 +0000
107 107 | summary: does not commute with e
108 108 |
109 109 o changeset: 6:500cac37a696
110 110 | user: test
111 111 | date: Thu Jan 01 00:00:00 1970 +0000
112 112 | summary: f
113 113 |
114 114 o changeset: 5:7b4e2f4b7bcd
115 115 | user: test
116 116 | date: Thu Jan 01 00:00:00 1970 +0000
117 117 | summary: e
118 118 |
119 119 o changeset: 4:00f1c5383965
120 120 | user: test
121 121 | date: Thu Jan 01 00:00:00 1970 +0000
122 122 | summary: d
123 123 |
124 124 o changeset: 3:65a9a84f33fd
125 125 | user: test
126 126 | date: Thu Jan 01 00:00:00 1970 +0000
127 127 | summary: c
128 128 |
129 129 o changeset: 2:da6535b52e45
130 130 | user: test
131 131 | date: Thu Jan 01 00:00:00 1970 +0000
132 132 | summary: b
133 133 |
134 134 o changeset: 1:c1f09da44841
135 135 | user: test
136 136 | date: Thu Jan 01 00:00:00 1970 +0000
137 137 | summary: a
138 138 |
139 139 o changeset: 0:1715188a53c7
140 140 user: test
141 141 date: Thu Jan 01 00:00:00 1970 +0000
142 142 summary: Initial commit
143 143
144 144
145 145 edit the history
146 146 $ hg histedit 3 --commands $EDITED 2>&1 | fixbundle
147 147 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
148 148 merging e
149 149 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
150 150 Fix up the change and run hg histedit --continue
151 151
152 152 fix up
153 153 $ echo 'I can haz no commute' > e
154 154 $ hg resolve --mark e
155 155 (no more unresolved files)
156 continue: hg histedit --continue
156 157 $ hg histedit --continue 2>&1 | fixbundle
157 158 merging e
158 159 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
159 160 Fix up the change and run hg histedit --continue
160 161
161 162 This failure is caused by 7b4e2f4b7bcd "e" not rebasing the non commutative
162 163 former children.
163 164
164 165 just continue this time
165 166 $ hg revert -r 'p1()' e
166 167 $ hg resolve --mark e
167 168 (no more unresolved files)
169 continue: hg histedit --continue
168 170 $ hg histedit --continue 2>&1 | fixbundle
169 171 7b4e2f4b7bcd: empty changeset
170 172
171 173 log after edit
172 174 $ hg log --graph
173 175 @ changeset: 6:7efe1373e4bc
174 176 | tag: tip
175 177 | user: test
176 178 | date: Thu Jan 01 00:00:00 1970 +0000
177 179 | summary: f
178 180 |
179 181 o changeset: 5:e334d87a1e55
180 182 | user: test
181 183 | date: Thu Jan 01 00:00:00 1970 +0000
182 184 | summary: does not commute with e
183 185 |
184 186 o changeset: 4:00f1c5383965
185 187 | user: test
186 188 | date: Thu Jan 01 00:00:00 1970 +0000
187 189 | summary: d
188 190 |
189 191 o changeset: 3:65a9a84f33fd
190 192 | user: test
191 193 | date: Thu Jan 01 00:00:00 1970 +0000
192 194 | summary: c
193 195 |
194 196 o changeset: 2:da6535b52e45
195 197 | user: test
196 198 | date: Thu Jan 01 00:00:00 1970 +0000
197 199 | summary: b
198 200 |
199 201 o changeset: 1:c1f09da44841
200 202 | user: test
201 203 | date: Thu Jan 01 00:00:00 1970 +0000
202 204 | summary: a
203 205 |
204 206 o changeset: 0:1715188a53c7
205 207 user: test
206 208 date: Thu Jan 01 00:00:00 1970 +0000
207 209 summary: Initial commit
208 210
209 211
210 212 start over
211 213
212 214 $ cd ..
213 215
214 216 $ initrepo r2
215 217 $ cd r2
216 218 $ rm $EDITED
217 219 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 3 >> $EDITED
218 220 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 4 >> $EDITED
219 221 $ hg log --template 'mess {node|short} {rev} {desc}\n' -r 7 >> $EDITED
220 222 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 5 >> $EDITED
221 223 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 6 >> $EDITED
222 224 $ cat $EDITED
223 225 pick 65a9a84f33fd 3 c
224 226 pick 00f1c5383965 4 d
225 227 mess 39522b764e3d 7 does not commute with e
226 228 pick 7b4e2f4b7bcd 5 e
227 229 pick 500cac37a696 6 f
228 230
229 231 edit the history, this time with a fold action
230 232 $ hg histedit 3 --commands $EDITED 2>&1 | fixbundle
231 233 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
232 234 merging e
233 235 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
234 236 Fix up the change and run hg histedit --continue
235 237
236 238 $ echo 'I can haz no commute' > e
237 239 $ hg resolve --mark e
238 240 (no more unresolved files)
241 continue: hg histedit --continue
239 242 $ hg histedit --continue 2>&1 | fixbundle
240 243 merging e
241 244 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
242 245 Fix up the change and run hg histedit --continue
243 246 second edit also fails, but just continue
244 247 $ hg revert -r 'p1()' e
245 248 $ hg resolve --mark e
246 249 (no more unresolved files)
250 continue: hg histedit --continue
247 251 $ hg histedit --continue 2>&1 | fixbundle
248 252 7b4e2f4b7bcd: empty changeset
249 253
250 254 post message fix
251 255 $ hg log --graph
252 256 @ changeset: 6:7efe1373e4bc
253 257 | tag: tip
254 258 | user: test
255 259 | date: Thu Jan 01 00:00:00 1970 +0000
256 260 | summary: f
257 261 |
258 262 o changeset: 5:e334d87a1e55
259 263 | user: test
260 264 | date: Thu Jan 01 00:00:00 1970 +0000
261 265 | summary: does not commute with e
262 266 |
263 267 o changeset: 4:00f1c5383965
264 268 | user: test
265 269 | date: Thu Jan 01 00:00:00 1970 +0000
266 270 | summary: d
267 271 |
268 272 o changeset: 3:65a9a84f33fd
269 273 | user: test
270 274 | date: Thu Jan 01 00:00:00 1970 +0000
271 275 | summary: c
272 276 |
273 277 o changeset: 2:da6535b52e45
274 278 | user: test
275 279 | date: Thu Jan 01 00:00:00 1970 +0000
276 280 | summary: b
277 281 |
278 282 o changeset: 1:c1f09da44841
279 283 | user: test
280 284 | date: Thu Jan 01 00:00:00 1970 +0000
281 285 | summary: a
282 286 |
283 287 o changeset: 0:1715188a53c7
284 288 user: test
285 289 date: Thu Jan 01 00:00:00 1970 +0000
286 290 summary: Initial commit
287 291
288 292
289 293 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now