##// END OF EJS Templates
histedit: prefer edit commit, edit message, use commit...
timeless -
r27674:78d86664 default
parent child Browse files
Show More
@@ -1,1478 +1,1479 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 def geteditcomment(first, last):
218 218 """ construct the editor comment
219 219 The comment includes::
220 220 - an intro
221 - short commands
221 - sorted primary commands
222 - sorted short commands
222 223
223 224 Commands are only included once.
224 225 """
225 226 intro = _("""Edit history between %s and %s
226 227
227 228 Commits are listed from least to most recent
228 229
229 230 Commands:""")
230 231 # i18n: command names and abbreviations must remain untranslated
231 232 verbs = _("""
233 e, edit = use commit, but stop for amending
234 m, mess = edit commit message without changing commit content
232 235 p, pick = use commit
233 e, edit = use commit, but stop for amending
236 d, drop = remove commit from history
234 237 f, fold = use commit, but combine it with the one above
235 238 r, roll = like fold, but discard this commit's description
236 d, drop = remove commit from history
237 m, mess = edit commit message without changing commit content
238 239 """)
239 240
240 241 return ''.join(['# %s\n' % l if l else '#\n'
241 242 for l in ((intro % (first, last) + verbs).split('\n'))])
242 243
243 244 class histeditstate(object):
244 245 def __init__(self, repo, parentctxnode=None, actions=None, keep=None,
245 246 topmost=None, replacements=None, lock=None, wlock=None):
246 247 self.repo = repo
247 248 self.actions = actions
248 249 self.keep = keep
249 250 self.topmost = topmost
250 251 self.parentctxnode = parentctxnode
251 252 self.lock = lock
252 253 self.wlock = wlock
253 254 self.backupfile = None
254 255 if replacements is None:
255 256 self.replacements = []
256 257 else:
257 258 self.replacements = replacements
258 259
259 260 def read(self):
260 261 """Load histedit state from disk and set fields appropriately."""
261 262 try:
262 263 state = self.repo.vfs.read('histedit-state')
263 264 except IOError as err:
264 265 if err.errno != errno.ENOENT:
265 266 raise
266 267 raise error.Abort(_('no histedit in progress'))
267 268
268 269 if state.startswith('v1\n'):
269 270 data = self._load()
270 271 parentctxnode, rules, keep, topmost, replacements, backupfile = data
271 272 else:
272 273 data = pickle.loads(state)
273 274 parentctxnode, rules, keep, topmost, replacements = data
274 275 backupfile = None
275 276
276 277 self.parentctxnode = parentctxnode
277 278 rules = "\n".join(["%s %s" % (verb, rest) for [verb, rest] in rules])
278 279 actions = parserules(rules, self)
279 280 self.actions = actions
280 281 self.keep = keep
281 282 self.topmost = topmost
282 283 self.replacements = replacements
283 284 self.backupfile = backupfile
284 285
285 286 def write(self):
286 287 fp = self.repo.vfs('histedit-state', 'w')
287 288 fp.write('v1\n')
288 289 fp.write('%s\n' % node.hex(self.parentctxnode))
289 290 fp.write('%s\n' % node.hex(self.topmost))
290 291 fp.write('%s\n' % self.keep)
291 292 fp.write('%d\n' % len(self.actions))
292 293 for action in self.actions:
293 294 fp.write('%s\n' % action.tostate())
294 295 fp.write('%d\n' % len(self.replacements))
295 296 for replacement in self.replacements:
296 297 fp.write('%s%s\n' % (node.hex(replacement[0]), ''.join(node.hex(r)
297 298 for r in replacement[1])))
298 299 backupfile = self.backupfile
299 300 if not backupfile:
300 301 backupfile = ''
301 302 fp.write('%s\n' % backupfile)
302 303 fp.close()
303 304
304 305 def _load(self):
305 306 fp = self.repo.vfs('histedit-state', 'r')
306 307 lines = [l[:-1] for l in fp.readlines()]
307 308
308 309 index = 0
309 310 lines[index] # version number
310 311 index += 1
311 312
312 313 parentctxnode = node.bin(lines[index])
313 314 index += 1
314 315
315 316 topmost = node.bin(lines[index])
316 317 index += 1
317 318
318 319 keep = lines[index] == 'True'
319 320 index += 1
320 321
321 322 # Rules
322 323 rules = []
323 324 rulelen = int(lines[index])
324 325 index += 1
325 326 for i in xrange(rulelen):
326 327 ruleaction = lines[index]
327 328 index += 1
328 329 rule = lines[index]
329 330 index += 1
330 331 rules.append((ruleaction, rule))
331 332
332 333 # Replacements
333 334 replacements = []
334 335 replacementlen = int(lines[index])
335 336 index += 1
336 337 for i in xrange(replacementlen):
337 338 replacement = lines[index]
338 339 original = node.bin(replacement[:40])
339 340 succ = [node.bin(replacement[i:i + 40]) for i in
340 341 range(40, len(replacement), 40)]
341 342 replacements.append((original, succ))
342 343 index += 1
343 344
344 345 backupfile = lines[index]
345 346 index += 1
346 347
347 348 fp.close()
348 349
349 350 return parentctxnode, rules, keep, topmost, replacements, backupfile
350 351
351 352 def clear(self):
352 353 if self.inprogress():
353 354 self.repo.vfs.unlink('histedit-state')
354 355
355 356 def inprogress(self):
356 357 return self.repo.vfs.exists('histedit-state')
357 358
358 359
359 360 class histeditaction(object):
360 361 def __init__(self, state, node):
361 362 self.state = state
362 363 self.repo = state.repo
363 364 self.node = node
364 365
365 366 @classmethod
366 367 def fromrule(cls, state, rule):
367 368 """Parses the given rule, returning an instance of the histeditaction.
368 369 """
369 370 rulehash = rule.strip().split(' ', 1)[0]
370 371 try:
371 372 rev = node.bin(rulehash)
372 373 except TypeError:
373 374 raise error.ParseError("invalid changeset %s" % rulehash)
374 375 return cls(state, rev)
375 376
376 377 def verify(self, prev):
377 378 """ Verifies semantic correctness of the rule"""
378 379 repo = self.repo
379 380 ha = node.hex(self.node)
380 381 try:
381 382 self.node = repo[ha].node()
382 383 except error.RepoError:
383 384 raise error.ParseError(_('unknown changeset %s listed')
384 385 % ha[:12])
385 386
386 387 def torule(self):
387 388 """build a histedit rule line for an action
388 389
389 390 by default lines are in the form:
390 391 <hash> <rev> <summary>
391 392 """
392 393 ctx = self.repo[self.node]
393 394 summary = ''
394 395 if ctx.description():
395 396 summary = ctx.description().splitlines()[0]
396 397 line = '%s %s %d %s' % (self.verb, ctx, ctx.rev(), summary)
397 398 # trim to 75 columns by default so it's not stupidly wide in my editor
398 399 # (the 5 more are left for verb)
399 400 maxlen = self.repo.ui.configint('histedit', 'linelen', default=80)
400 401 maxlen = max(maxlen, 22) # avoid truncating hash
401 402 return util.ellipsis(line, maxlen)
402 403
403 404 def tostate(self):
404 405 """Print an action in format used by histedit state files
405 406 (the first line is a verb, the remainder is the second)
406 407 """
407 408 return "%s\n%s" % (self.verb, node.hex(self.node))
408 409
409 410 def constraints(self):
410 411 """Return a set of constrains that this action should be verified for
411 412 """
412 413 return set([_constraints.noduplicates, _constraints.noother])
413 414
414 415 def nodetoverify(self):
415 416 """Returns a node associated with the action that will be used for
416 417 verification purposes.
417 418
418 419 If the action doesn't correspond to node it should return None
419 420 """
420 421 return self.node
421 422
422 423 def run(self):
423 424 """Runs the action. The default behavior is simply apply the action's
424 425 rulectx onto the current parentctx."""
425 426 self.applychange()
426 427 self.continuedirty()
427 428 return self.continueclean()
428 429
429 430 def applychange(self):
430 431 """Applies the changes from this action's rulectx onto the current
431 432 parentctx, but does not commit them."""
432 433 repo = self.repo
433 434 rulectx = repo[self.node]
434 435 hg.update(repo, self.state.parentctxnode, quietempty=True)
435 436 stats = applychanges(repo.ui, repo, rulectx, {})
436 437 if stats and stats[3] > 0:
437 438 raise error.InterventionRequired(
438 439 _('Fix up the change (%s %s)') %
439 440 (self.verb, node.short(self.node)),
440 441 hint=_('hg histedit --continue to resume'))
441 442
442 443 def continuedirty(self):
443 444 """Continues the action when changes have been applied to the working
444 445 copy. The default behavior is to commit the dirty changes."""
445 446 repo = self.repo
446 447 rulectx = repo[self.node]
447 448
448 449 editor = self.commiteditor()
449 450 commit = commitfuncfor(repo, rulectx)
450 451
451 452 commit(text=rulectx.description(), user=rulectx.user(),
452 453 date=rulectx.date(), extra=rulectx.extra(), editor=editor)
453 454
454 455 def commiteditor(self):
455 456 """The editor to be used to edit the commit message."""
456 457 return False
457 458
458 459 def continueclean(self):
459 460 """Continues the action when the working copy is clean. The default
460 461 behavior is to accept the current commit as the new version of the
461 462 rulectx."""
462 463 ctx = self.repo['.']
463 464 if ctx.node() == self.state.parentctxnode:
464 465 self.repo.ui.warn(_('%s: empty changeset\n') %
465 466 node.short(self.node))
466 467 return ctx, [(self.node, tuple())]
467 468 if ctx.node() == self.node:
468 469 # Nothing changed
469 470 return ctx, []
470 471 return ctx, [(self.node, (ctx.node(),))]
471 472
472 473 def commitfuncfor(repo, src):
473 474 """Build a commit function for the replacement of <src>
474 475
475 476 This function ensure we apply the same treatment to all changesets.
476 477
477 478 - Add a 'histedit_source' entry in extra.
478 479
479 480 Note that fold has its own separated logic because its handling is a bit
480 481 different and not easily factored out of the fold method.
481 482 """
482 483 phasemin = src.phase()
483 484 def commitfunc(**kwargs):
484 485 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
485 486 try:
486 487 repo.ui.setconfig('phases', 'new-commit', phasemin,
487 488 'histedit')
488 489 extra = kwargs.get('extra', {}).copy()
489 490 extra['histedit_source'] = src.hex()
490 491 kwargs['extra'] = extra
491 492 return repo.commit(**kwargs)
492 493 finally:
493 494 repo.ui.restoreconfig(phasebackup)
494 495 return commitfunc
495 496
496 497 def applychanges(ui, repo, ctx, opts):
497 498 """Merge changeset from ctx (only) in the current working directory"""
498 499 wcpar = repo.dirstate.parents()[0]
499 500 if ctx.p1().node() == wcpar:
500 501 # edits are "in place" we do not need to make any merge,
501 502 # just applies changes on parent for editing
502 503 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
503 504 stats = None
504 505 else:
505 506 try:
506 507 # ui.forcemerge is an internal variable, do not document
507 508 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
508 509 'histedit')
509 510 stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'histedit'])
510 511 finally:
511 512 repo.ui.setconfig('ui', 'forcemerge', '', 'histedit')
512 513 return stats
513 514
514 515 def collapse(repo, first, last, commitopts, skipprompt=False):
515 516 """collapse the set of revisions from first to last as new one.
516 517
517 518 Expected commit options are:
518 519 - message
519 520 - date
520 521 - username
521 522 Commit message is edited in all cases.
522 523
523 524 This function works in memory."""
524 525 ctxs = list(repo.set('%d::%d', first, last))
525 526 if not ctxs:
526 527 return None
527 528 for c in ctxs:
528 529 if not c.mutable():
529 530 raise error.ParseError(
530 531 _("cannot fold into public change %s") % node.short(c.node()))
531 532 base = first.parents()[0]
532 533
533 534 # commit a new version of the old changeset, including the update
534 535 # collect all files which might be affected
535 536 files = set()
536 537 for ctx in ctxs:
537 538 files.update(ctx.files())
538 539
539 540 # Recompute copies (avoid recording a -> b -> a)
540 541 copied = copies.pathcopies(base, last)
541 542
542 543 # prune files which were reverted by the updates
543 544 def samefile(f):
544 545 if f in last.manifest():
545 546 a = last.filectx(f)
546 547 if f in base.manifest():
547 548 b = base.filectx(f)
548 549 return (a.data() == b.data()
549 550 and a.flags() == b.flags())
550 551 else:
551 552 return False
552 553 else:
553 554 return f not in base.manifest()
554 555 files = [f for f in files if not samefile(f)]
555 556 # commit version of these files as defined by head
556 557 headmf = last.manifest()
557 558 def filectxfn(repo, ctx, path):
558 559 if path in headmf:
559 560 fctx = last[path]
560 561 flags = fctx.flags()
561 562 mctx = context.memfilectx(repo,
562 563 fctx.path(), fctx.data(),
563 564 islink='l' in flags,
564 565 isexec='x' in flags,
565 566 copied=copied.get(path))
566 567 return mctx
567 568 return None
568 569
569 570 if commitopts.get('message'):
570 571 message = commitopts['message']
571 572 else:
572 573 message = first.description()
573 574 user = commitopts.get('user')
574 575 date = commitopts.get('date')
575 576 extra = commitopts.get('extra')
576 577
577 578 parents = (first.p1().node(), first.p2().node())
578 579 editor = None
579 580 if not skipprompt:
580 581 editor = cmdutil.getcommiteditor(edit=True, editform='histedit.fold')
581 582 new = context.memctx(repo,
582 583 parents=parents,
583 584 text=message,
584 585 files=files,
585 586 filectxfn=filectxfn,
586 587 user=user,
587 588 date=date,
588 589 extra=extra,
589 590 editor=editor)
590 591 return repo.commitctx(new)
591 592
592 593 def _isdirtywc(repo):
593 594 return repo[None].dirty(missing=True)
594 595
595 596 def abortdirty():
596 597 raise error.Abort(_('working copy has pending changes'),
597 598 hint=_('amend, commit, or revert them and run histedit '
598 599 '--continue, or abort with histedit --abort'))
599 600
600 601
601 602 actiontable = {}
602 603 actionlist = []
603 604
604 605 def addhisteditaction(verbs):
605 606 def wrap(cls):
606 607 cls.verb = verbs[0]
607 608 for verb in verbs:
608 609 actiontable[verb] = cls
609 610 actionlist.append(cls)
610 611 return cls
611 612 return wrap
612 613
613 614
614 615 @addhisteditaction(['pick', 'p'])
615 616 class pick(histeditaction):
616 617 def run(self):
617 618 rulectx = self.repo[self.node]
618 619 if rulectx.parents()[0].node() == self.state.parentctxnode:
619 620 self.repo.ui.debug('node %s unchanged\n' % node.short(self.node))
620 621 return rulectx, []
621 622
622 623 return super(pick, self).run()
623 624
624 625 @addhisteditaction(['edit', 'e'])
625 626 class edit(histeditaction):
626 627 def run(self):
627 628 repo = self.repo
628 629 rulectx = repo[self.node]
629 630 hg.update(repo, self.state.parentctxnode, quietempty=True)
630 631 applychanges(repo.ui, repo, rulectx, {})
631 632 raise error.InterventionRequired(
632 633 _('Editing (%s), you may commit or record as needed now.')
633 634 % node.short(self.node),
634 635 hint=_('hg histedit --continue to resume'))
635 636
636 637 def commiteditor(self):
637 638 return cmdutil.getcommiteditor(edit=True, editform='histedit.edit')
638 639
639 640 @addhisteditaction(['fold', 'f'])
640 641 class fold(histeditaction):
641 642 def verify(self, prev):
642 643 """ Verifies semantic correctness of the fold rule"""
643 644 super(fold, self).verify(prev)
644 645 repo = self.repo
645 646 if not prev:
646 647 c = repo[self.node].parents()[0]
647 648 elif not prev.verb in ('pick', 'base'):
648 649 return
649 650 else:
650 651 c = repo[prev.node]
651 652 if not c.mutable():
652 653 raise error.ParseError(
653 654 _("cannot fold into public change %s") % node.short(c.node()))
654 655
655 656
656 657 def continuedirty(self):
657 658 repo = self.repo
658 659 rulectx = repo[self.node]
659 660
660 661 commit = commitfuncfor(repo, rulectx)
661 662 commit(text='fold-temp-revision %s' % node.short(self.node),
662 663 user=rulectx.user(), date=rulectx.date(),
663 664 extra=rulectx.extra())
664 665
665 666 def continueclean(self):
666 667 repo = self.repo
667 668 ctx = repo['.']
668 669 rulectx = repo[self.node]
669 670 parentctxnode = self.state.parentctxnode
670 671 if ctx.node() == parentctxnode:
671 672 repo.ui.warn(_('%s: empty changeset\n') %
672 673 node.short(self.node))
673 674 return ctx, [(self.node, (parentctxnode,))]
674 675
675 676 parentctx = repo[parentctxnode]
676 677 newcommits = set(c.node() for c in repo.set('(%d::. - %d)', parentctx,
677 678 parentctx))
678 679 if not newcommits:
679 680 repo.ui.warn(_('%s: cannot fold - working copy is not a '
680 681 'descendant of previous commit %s\n') %
681 682 (node.short(self.node), node.short(parentctxnode)))
682 683 return ctx, [(self.node, (ctx.node(),))]
683 684
684 685 middlecommits = newcommits.copy()
685 686 middlecommits.discard(ctx.node())
686 687
687 688 return self.finishfold(repo.ui, repo, parentctx, rulectx, ctx.node(),
688 689 middlecommits)
689 690
690 691 def skipprompt(self):
691 692 """Returns true if the rule should skip the message editor.
692 693
693 694 For example, 'fold' wants to show an editor, but 'rollup'
694 695 doesn't want to.
695 696 """
696 697 return False
697 698
698 699 def mergedescs(self):
699 700 """Returns true if the rule should merge messages of multiple changes.
700 701
701 702 This exists mainly so that 'rollup' rules can be a subclass of
702 703 'fold'.
703 704 """
704 705 return True
705 706
706 707 def finishfold(self, ui, repo, ctx, oldctx, newnode, internalchanges):
707 708 parent = ctx.parents()[0].node()
708 709 hg.update(repo, parent)
709 710 ### prepare new commit data
710 711 commitopts = {}
711 712 commitopts['user'] = ctx.user()
712 713 # commit message
713 714 if not self.mergedescs():
714 715 newmessage = ctx.description()
715 716 else:
716 717 newmessage = '\n***\n'.join(
717 718 [ctx.description()] +
718 719 [repo[r].description() for r in internalchanges] +
719 720 [oldctx.description()]) + '\n'
720 721 commitopts['message'] = newmessage
721 722 # date
722 723 commitopts['date'] = max(ctx.date(), oldctx.date())
723 724 extra = ctx.extra().copy()
724 725 # histedit_source
725 726 # note: ctx is likely a temporary commit but that the best we can do
726 727 # here. This is sufficient to solve issue3681 anyway.
727 728 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex())
728 729 commitopts['extra'] = extra
729 730 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
730 731 try:
731 732 phasemin = max(ctx.phase(), oldctx.phase())
732 733 repo.ui.setconfig('phases', 'new-commit', phasemin, 'histedit')
733 734 n = collapse(repo, ctx, repo[newnode], commitopts,
734 735 skipprompt=self.skipprompt())
735 736 finally:
736 737 repo.ui.restoreconfig(phasebackup)
737 738 if n is None:
738 739 return ctx, []
739 740 hg.update(repo, n)
740 741 replacements = [(oldctx.node(), (newnode,)),
741 742 (ctx.node(), (n,)),
742 743 (newnode, (n,)),
743 744 ]
744 745 for ich in internalchanges:
745 746 replacements.append((ich, (n,)))
746 747 return repo[n], replacements
747 748
748 749 class base(histeditaction):
749 750 def constraints(self):
750 751 return set([_constraints.forceother])
751 752
752 753 def run(self):
753 754 if self.repo['.'].node() != self.node:
754 755 mergemod.update(self.repo, self.node, False, True)
755 756 # branchmerge, force)
756 757 return self.continueclean()
757 758
758 759 def continuedirty(self):
759 760 abortdirty()
760 761
761 762 def continueclean(self):
762 763 basectx = self.repo['.']
763 764 return basectx, []
764 765
765 766 @addhisteditaction(['_multifold'])
766 767 class _multifold(fold):
767 768 """fold subclass used for when multiple folds happen in a row
768 769
769 770 We only want to fire the editor for the folded message once when
770 771 (say) four changes are folded down into a single change. This is
771 772 similar to rollup, but we should preserve both messages so that
772 773 when the last fold operation runs we can show the user all the
773 774 commit messages in their editor.
774 775 """
775 776 def skipprompt(self):
776 777 return True
777 778
778 779 @addhisteditaction(["roll", "r"])
779 780 class rollup(fold):
780 781 def mergedescs(self):
781 782 return False
782 783
783 784 def skipprompt(self):
784 785 return True
785 786
786 787 @addhisteditaction(["drop", "d"])
787 788 class drop(histeditaction):
788 789 def run(self):
789 790 parentctx = self.repo[self.state.parentctxnode]
790 791 return parentctx, [(self.node, tuple())]
791 792
792 793 @addhisteditaction(["mess", "m"])
793 794 class message(histeditaction):
794 795 def commiteditor(self):
795 796 return cmdutil.getcommiteditor(edit=True, editform='histedit.mess')
796 797
797 798 def findoutgoing(ui, repo, remote=None, force=False, opts=None):
798 799 """utility function to find the first outgoing changeset
799 800
800 801 Used by initialization code"""
801 802 if opts is None:
802 803 opts = {}
803 804 dest = ui.expandpath(remote or 'default-push', remote or 'default')
804 805 dest, revs = hg.parseurl(dest, None)[:2]
805 806 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
806 807
807 808 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
808 809 other = hg.peer(repo, opts, dest)
809 810
810 811 if revs:
811 812 revs = [repo.lookup(rev) for rev in revs]
812 813
813 814 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
814 815 if not outgoing.missing:
815 816 raise error.Abort(_('no outgoing ancestors'))
816 817 roots = list(repo.revs("roots(%ln)", outgoing.missing))
817 818 if 1 < len(roots):
818 819 msg = _('there are ambiguous outgoing revisions')
819 820 hint = _('see "hg help histedit" for more detail')
820 821 raise error.Abort(msg, hint=hint)
821 822 return repo.lookup(roots[0])
822 823
823 824
824 825 @command('histedit',
825 826 [('', 'commands', '',
826 827 _('read history edits from the specified file'), _('FILE')),
827 828 ('c', 'continue', False, _('continue an edit already in progress')),
828 829 ('', 'edit-plan', False, _('edit remaining actions list')),
829 830 ('k', 'keep', False,
830 831 _("don't strip old nodes after edit is complete")),
831 832 ('', 'abort', False, _('abort an edit in progress')),
832 833 ('o', 'outgoing', False, _('changesets not found in destination')),
833 834 ('f', 'force', False,
834 835 _('force outgoing even for unrelated repositories')),
835 836 ('r', 'rev', [], _('first revision to be edited'), _('REV'))],
836 837 _("[ANCESTOR] | --outgoing [URL]"))
837 838 def histedit(ui, repo, *freeargs, **opts):
838 839 """interactively edit changeset history
839 840
840 841 This command edits changesets between an ANCESTOR and the parent of
841 842 the working directory.
842 843
843 844 The value from the "histedit.defaultrev" config option is used as a
844 845 revset to select the base revision when ANCESTOR is not specified.
845 846 The first revision returned by the revset is used. By default, this
846 847 selects the editable history that is unique to the ancestry of the
847 848 working directory.
848 849
849 850 With --outgoing, this edits changesets not found in the
850 851 destination repository. If URL of the destination is omitted, the
851 852 'default-push' (or 'default') path will be used.
852 853
853 854 .. container:: verbose
854 855
855 856 If you use --outgoing, this command will abort if there are ambiguous
856 857 outgoing revisions. For example, if there are multiple branches
857 858 containing outgoing revisions.
858 859
859 860 Use "min(outgoing() and ::.)" or similar revset specification
860 861 instead of --outgoing to specify edit target revision exactly in
861 862 such ambiguous situation. See :hg:`help revsets` for detail about
862 863 selecting revisions.
863 864
864 865 .. container:: verbose
865 866
866 867 Examples:
867 868
868 869 - A number of changes have been made.
869 870 Revision 3 is no longer needed.
870 871
871 872 Start history editing from revision 3::
872 873
873 874 hg histedit -r 3
874 875
875 876 An editor opens, containing the list of revisions,
876 877 with specific actions specified::
877 878
878 879 pick 5339bf82f0ca 3 Zworgle the foobar
879 880 pick 8ef592ce7cc4 4 Bedazzle the zerlog
880 881 pick 0a9639fcda9d 5 Morgify the cromulancy
881 882
882 883 Additional information about the possible actions
883 884 to take appears below the list of revisions.
884 885
885 886 To remove revision 3 from the history,
886 887 its action (at the beginning of the relevant line)
887 888 is changed to 'drop'::
888 889
889 890 drop 5339bf82f0ca 3 Zworgle the foobar
890 891 pick 8ef592ce7cc4 4 Bedazzle the zerlog
891 892 pick 0a9639fcda9d 5 Morgify the cromulancy
892 893
893 894 - A number of changes have been made.
894 895 Revision 2 and 4 need to be swapped.
895 896
896 897 Start history editing from revision 2::
897 898
898 899 hg histedit -r 2
899 900
900 901 An editor opens, containing the list of revisions,
901 902 with specific actions specified::
902 903
903 904 pick 252a1af424ad 2 Blorb a morgwazzle
904 905 pick 5339bf82f0ca 3 Zworgle the foobar
905 906 pick 8ef592ce7cc4 4 Bedazzle the zerlog
906 907
907 908 To swap revision 2 and 4, its lines are swapped
908 909 in the editor::
909 910
910 911 pick 8ef592ce7cc4 4 Bedazzle the zerlog
911 912 pick 5339bf82f0ca 3 Zworgle the foobar
912 913 pick 252a1af424ad 2 Blorb a morgwazzle
913 914
914 915 Returns 0 on success, 1 if user intervention is required (not only
915 916 for intentional "edit" command, but also for resolving unexpected
916 917 conflicts).
917 918 """
918 919 state = histeditstate(repo)
919 920 try:
920 921 state.wlock = repo.wlock()
921 922 state.lock = repo.lock()
922 923 _histedit(ui, repo, state, *freeargs, **opts)
923 924 finally:
924 925 release(state.lock, state.wlock)
925 926
926 927 def _histedit(ui, repo, state, *freeargs, **opts):
927 928 # TODO only abort if we try to histedit mq patches, not just
928 929 # blanket if mq patches are applied somewhere
929 930 mq = getattr(repo, 'mq', None)
930 931 if mq and mq.applied:
931 932 raise error.Abort(_('source has mq patches applied'))
932 933
933 934 # basic argument incompatibility processing
934 935 outg = opts.get('outgoing')
935 936 cont = opts.get('continue')
936 937 editplan = opts.get('edit_plan')
937 938 abort = opts.get('abort')
938 939 force = opts.get('force')
939 940 rules = opts.get('commands', '')
940 941 revs = opts.get('rev', [])
941 942 goal = 'new' # This invocation goal, in new, continue, abort
942 943 if force and not outg:
943 944 raise error.Abort(_('--force only allowed with --outgoing'))
944 945 if cont:
945 946 if any((outg, abort, revs, freeargs, rules, editplan)):
946 947 raise error.Abort(_('no arguments allowed with --continue'))
947 948 goal = 'continue'
948 949 elif abort:
949 950 if any((outg, revs, freeargs, rules, editplan)):
950 951 raise error.Abort(_('no arguments allowed with --abort'))
951 952 goal = 'abort'
952 953 elif editplan:
953 954 if any((outg, revs, freeargs)):
954 955 raise error.Abort(_('only --commands argument allowed with '
955 956 '--edit-plan'))
956 957 goal = 'edit-plan'
957 958 else:
958 959 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
959 960 raise error.Abort(_('history edit already in progress, try '
960 961 '--continue or --abort'))
961 962 if outg:
962 963 if revs:
963 964 raise error.Abort(_('no revisions allowed with --outgoing'))
964 965 if len(freeargs) > 1:
965 966 raise error.Abort(
966 967 _('only one repo argument allowed with --outgoing'))
967 968 else:
968 969 revs.extend(freeargs)
969 970 if len(revs) == 0:
970 971 defaultrev = destutil.desthistedit(ui, repo)
971 972 if defaultrev is not None:
972 973 revs.append(defaultrev)
973 974
974 975 if len(revs) != 1:
975 976 raise error.Abort(
976 977 _('histedit requires exactly one ancestor revision'))
977 978
978 979
979 980 replacements = []
980 981 state.keep = opts.get('keep', False)
981 982 supportsmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
982 983
983 984 # rebuild state
984 985 if goal == 'continue':
985 986 state.read()
986 987 state = bootstrapcontinue(ui, state, opts)
987 988 elif goal == 'edit-plan':
988 989 state.read()
989 990 if not rules:
990 991 comment = geteditcomment(node.short(state.parentctxnode),
991 992 node.short(state.topmost))
992 993 rules = ruleeditor(repo, ui, state.actions, comment)
993 994 else:
994 995 if rules == '-':
995 996 f = sys.stdin
996 997 else:
997 998 f = open(rules)
998 999 rules = f.read()
999 1000 f.close()
1000 1001 actions = parserules(rules, state)
1001 1002 ctxs = [repo[act.nodetoverify()] \
1002 1003 for act in state.actions if act.nodetoverify()]
1003 1004 warnverifyactions(ui, repo, actions, state, ctxs)
1004 1005 state.actions = actions
1005 1006 state.write()
1006 1007 return
1007 1008 elif goal == 'abort':
1008 1009 try:
1009 1010 state.read()
1010 1011 tmpnodes, leafs = newnodestoabort(state)
1011 1012 ui.debug('restore wc to old parent %s\n'
1012 1013 % node.short(state.topmost))
1013 1014
1014 1015 # Recover our old commits if necessary
1015 1016 if not state.topmost in repo and state.backupfile:
1016 1017 backupfile = repo.join(state.backupfile)
1017 1018 f = hg.openpath(ui, backupfile)
1018 1019 gen = exchange.readbundle(ui, f, backupfile)
1019 1020 tr = repo.transaction('histedit.abort')
1020 1021 try:
1021 1022 if not isinstance(gen, bundle2.unbundle20):
1022 1023 gen.apply(repo, 'histedit', 'bundle:' + backupfile)
1023 1024 if isinstance(gen, bundle2.unbundle20):
1024 1025 bundle2.applybundle(repo, gen, tr,
1025 1026 source='histedit',
1026 1027 url='bundle:' + backupfile)
1027 1028 tr.close()
1028 1029 finally:
1029 1030 tr.release()
1030 1031
1031 1032 os.remove(backupfile)
1032 1033
1033 1034 # check whether we should update away
1034 1035 if repo.unfiltered().revs('parents() and (%n or %ln::)',
1035 1036 state.parentctxnode, leafs | tmpnodes):
1036 1037 hg.clean(repo, state.topmost, show_stats=True, quietempty=True)
1037 1038 cleanupnode(ui, repo, 'created', tmpnodes)
1038 1039 cleanupnode(ui, repo, 'temp', leafs)
1039 1040 except Exception:
1040 1041 if state.inprogress():
1041 1042 ui.warn(_('warning: encountered an exception during histedit '
1042 1043 '--abort; the repository may not have been completely '
1043 1044 'cleaned up\n'))
1044 1045 raise
1045 1046 finally:
1046 1047 state.clear()
1047 1048 return
1048 1049 else:
1049 1050 cmdutil.checkunfinished(repo)
1050 1051 cmdutil.bailifchanged(repo)
1051 1052
1052 1053 topmost, empty = repo.dirstate.parents()
1053 1054 if outg:
1054 1055 if freeargs:
1055 1056 remote = freeargs[0]
1056 1057 else:
1057 1058 remote = None
1058 1059 root = findoutgoing(ui, repo, remote, force, opts)
1059 1060 else:
1060 1061 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs)))
1061 1062 if len(rr) != 1:
1062 1063 raise error.Abort(_('The specified revisions must have '
1063 1064 'exactly one common root'))
1064 1065 root = rr[0].node()
1065 1066
1066 1067 revs = between(repo, root, topmost, state.keep)
1067 1068 if not revs:
1068 1069 raise error.Abort(_('%s is not an ancestor of working directory') %
1069 1070 node.short(root))
1070 1071
1071 1072 ctxs = [repo[r] for r in revs]
1072 1073 if not rules:
1073 1074 comment = geteditcomment(node.short(root), node.short(topmost))
1074 1075 actions = [pick(state, r) for r in revs]
1075 1076 rules = ruleeditor(repo, ui, actions, comment)
1076 1077 else:
1077 1078 if rules == '-':
1078 1079 f = sys.stdin
1079 1080 else:
1080 1081 f = open(rules)
1081 1082 rules = f.read()
1082 1083 f.close()
1083 1084 actions = parserules(rules, state)
1084 1085 warnverifyactions(ui, repo, actions, state, ctxs)
1085 1086
1086 1087 parentctxnode = repo[root].parents()[0].node()
1087 1088
1088 1089 state.parentctxnode = parentctxnode
1089 1090 state.actions = actions
1090 1091 state.topmost = topmost
1091 1092 state.replacements = replacements
1092 1093
1093 1094 # Create a backup so we can always abort completely.
1094 1095 backupfile = None
1095 1096 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1096 1097 backupfile = repair._bundle(repo, [parentctxnode], [topmost], root,
1097 1098 'histedit')
1098 1099 state.backupfile = backupfile
1099 1100
1100 1101 # preprocess rules so that we can hide inner folds from the user
1101 1102 # and only show one editor
1102 1103 actions = state.actions[:]
1103 1104 for idx, (action, nextact) in enumerate(
1104 1105 zip(actions, actions[1:] + [None])):
1105 1106 if action.verb == 'fold' and nextact and nextact.verb == 'fold':
1106 1107 state.actions[idx].__class__ = _multifold
1107 1108
1108 1109 total = len(state.actions)
1109 1110 pos = 0
1110 1111 while state.actions:
1111 1112 state.write()
1112 1113 actobj = state.actions.pop(0)
1113 1114 pos += 1
1114 1115 ui.progress(_("editing"), pos, actobj.torule(),
1115 1116 _('changes'), total)
1116 1117 ui.debug('histedit: processing %s %s\n' % (actobj.verb,\
1117 1118 actobj.torule()))
1118 1119 parentctx, replacement_ = actobj.run()
1119 1120 state.parentctxnode = parentctx.node()
1120 1121 state.replacements.extend(replacement_)
1121 1122 state.write()
1122 1123 ui.progress(_("editing"), None)
1123 1124
1124 1125 hg.update(repo, state.parentctxnode, quietempty=True)
1125 1126
1126 1127 mapping, tmpnodes, created, ntm = processreplacement(state)
1127 1128 if mapping:
1128 1129 for prec, succs in mapping.iteritems():
1129 1130 if not succs:
1130 1131 ui.debug('histedit: %s is dropped\n' % node.short(prec))
1131 1132 else:
1132 1133 ui.debug('histedit: %s is replaced by %s\n' % (
1133 1134 node.short(prec), node.short(succs[0])))
1134 1135 if len(succs) > 1:
1135 1136 m = 'histedit: %s'
1136 1137 for n in succs[1:]:
1137 1138 ui.debug(m % node.short(n))
1138 1139
1139 1140 if supportsmarkers:
1140 1141 # Only create markers if the temp nodes weren't already removed.
1141 1142 obsolete.createmarkers(repo, ((repo[t],()) for t in sorted(tmpnodes)
1142 1143 if t in repo))
1143 1144 else:
1144 1145 cleanupnode(ui, repo, 'temp', tmpnodes)
1145 1146
1146 1147 if not state.keep:
1147 1148 if mapping:
1148 1149 movebookmarks(ui, repo, mapping, state.topmost, ntm)
1149 1150 # TODO update mq state
1150 1151 if supportsmarkers:
1151 1152 markers = []
1152 1153 # sort by revision number because it sound "right"
1153 1154 for prec in sorted(mapping, key=repo.changelog.rev):
1154 1155 succs = mapping[prec]
1155 1156 markers.append((repo[prec],
1156 1157 tuple(repo[s] for s in succs)))
1157 1158 if markers:
1158 1159 obsolete.createmarkers(repo, markers)
1159 1160 else:
1160 1161 cleanupnode(ui, repo, 'replaced', mapping)
1161 1162
1162 1163 state.clear()
1163 1164 if os.path.exists(repo.sjoin('undo')):
1164 1165 os.unlink(repo.sjoin('undo'))
1165 1166 if repo.vfs.exists('histedit-last-edit.txt'):
1166 1167 repo.vfs.unlink('histedit-last-edit.txt')
1167 1168
1168 1169 def bootstrapcontinue(ui, state, opts):
1169 1170 repo = state.repo
1170 1171 if state.actions:
1171 1172 actobj = state.actions.pop(0)
1172 1173
1173 1174 if _isdirtywc(repo):
1174 1175 actobj.continuedirty()
1175 1176 if _isdirtywc(repo):
1176 1177 abortdirty()
1177 1178
1178 1179 parentctx, replacements = actobj.continueclean()
1179 1180
1180 1181 state.parentctxnode = parentctx.node()
1181 1182 state.replacements.extend(replacements)
1182 1183
1183 1184 return state
1184 1185
1185 1186 def between(repo, old, new, keep):
1186 1187 """select and validate the set of revision to edit
1187 1188
1188 1189 When keep is false, the specified set can't have children."""
1189 1190 ctxs = list(repo.set('%n::%n', old, new))
1190 1191 if ctxs and not keep:
1191 1192 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
1192 1193 repo.revs('(%ld::) - (%ld)', ctxs, ctxs)):
1193 1194 raise error.Abort(_('cannot edit history that would orphan nodes'))
1194 1195 if repo.revs('(%ld) and merge()', ctxs):
1195 1196 raise error.Abort(_('cannot edit history that contains merges'))
1196 1197 root = ctxs[0] # list is already sorted by repo.set
1197 1198 if not root.mutable():
1198 1199 raise error.Abort(_('cannot edit public changeset: %s') % root,
1199 1200 hint=_('see "hg help phases" for details'))
1200 1201 return [c.node() for c in ctxs]
1201 1202
1202 1203 def ruleeditor(repo, ui, actions, editcomment=""):
1203 1204 """open an editor to edit rules
1204 1205
1205 1206 rules are in the format [ [act, ctx], ...] like in state.rules
1206 1207 """
1207 1208 rules = '\n'.join([act.torule() for act in actions])
1208 1209 rules += '\n\n'
1209 1210 rules += editcomment
1210 1211 rules = ui.edit(rules, ui.username(), {'prefix': 'histedit'})
1211 1212
1212 1213 # Save edit rules in .hg/histedit-last-edit.txt in case
1213 1214 # the user needs to ask for help after something
1214 1215 # surprising happens.
1215 1216 f = open(repo.join('histedit-last-edit.txt'), 'w')
1216 1217 f.write(rules)
1217 1218 f.close()
1218 1219
1219 1220 return rules
1220 1221
1221 1222 def parserules(rules, state):
1222 1223 """Read the histedit rules string and return list of action objects """
1223 1224 rules = [l for l in (r.strip() for r in rules.splitlines())
1224 1225 if l and not l.startswith('#')]
1225 1226 actions = []
1226 1227 for r in rules:
1227 1228 if ' ' not in r:
1228 1229 raise error.ParseError(_('malformed line "%s"') % r)
1229 1230 verb, rest = r.split(' ', 1)
1230 1231
1231 1232 if verb not in actiontable:
1232 1233 raise error.ParseError(_('unknown action "%s"') % verb)
1233 1234
1234 1235 action = actiontable[verb].fromrule(state, rest)
1235 1236 actions.append(action)
1236 1237 return actions
1237 1238
1238 1239 def warnverifyactions(ui, repo, actions, state, ctxs):
1239 1240 try:
1240 1241 verifyactions(actions, state, ctxs)
1241 1242 except error.ParseError:
1242 1243 if repo.vfs.exists('histedit-last-edit.txt'):
1243 1244 ui.warn(_('warning: histedit rules saved '
1244 1245 'to: .hg/histedit-last-edit.txt\n'))
1245 1246 raise
1246 1247
1247 1248 def verifyactions(actions, state, ctxs):
1248 1249 """Verify that there exists exactly one action per given changeset and
1249 1250 other constraints.
1250 1251
1251 1252 Will abort if there are to many or too few rules, a malformed rule,
1252 1253 or a rule on a changeset outside of the user-given range.
1253 1254 """
1254 1255 expected = set(c.hex() for c in ctxs)
1255 1256 seen = set()
1256 1257 prev = None
1257 1258 for action in actions:
1258 1259 action.verify(prev)
1259 1260 prev = action
1260 1261 constraints = action.constraints()
1261 1262 for constraint in constraints:
1262 1263 if constraint not in _constraints.known():
1263 1264 raise error.ParseError(_('unknown constraint "%s"') %
1264 1265 constraint)
1265 1266
1266 1267 nodetoverify = action.nodetoverify()
1267 1268 if nodetoverify is not None:
1268 1269 ha = node.hex(nodetoverify)
1269 1270 if _constraints.noother in constraints and ha not in expected:
1270 1271 raise error.ParseError(
1271 1272 _('may not use "%s" with changesets '
1272 1273 'other than the ones listed') % action.verb)
1273 1274 if _constraints.forceother in constraints and ha in expected:
1274 1275 raise error.ParseError(
1275 1276 _('may not use "%s" with changesets '
1276 1277 'within the edited list') % action.verb)
1277 1278 if _constraints.noduplicates in constraints and ha in seen:
1278 1279 raise error.ParseError(_(
1279 1280 'duplicated command for changeset %s') %
1280 1281 ha[:12])
1281 1282 seen.add(ha)
1282 1283 missing = sorted(expected - seen) # sort to stabilize output
1283 1284
1284 1285 if state.repo.ui.configbool('histedit', 'dropmissing'):
1285 1286 drops = [drop(state, node.bin(n)) for n in missing]
1286 1287 # put the in the beginning so they execute immediately and
1287 1288 # don't show in the edit-plan in the future
1288 1289 actions[:0] = drops
1289 1290 elif missing:
1290 1291 raise error.ParseError(_('missing rules for changeset %s') %
1291 1292 missing[0][:12],
1292 1293 hint=_('use "drop %s" to discard, see also: '
1293 1294 '"hg help -e histedit.config"') % missing[0][:12])
1294 1295
1295 1296 def newnodestoabort(state):
1296 1297 """process the list of replacements to return
1297 1298
1298 1299 1) the list of final node
1299 1300 2) the list of temporary node
1300 1301
1301 1302 This is meant to be used on abort as less data are required in this case.
1302 1303 """
1303 1304 replacements = state.replacements
1304 1305 allsuccs = set()
1305 1306 replaced = set()
1306 1307 for rep in replacements:
1307 1308 allsuccs.update(rep[1])
1308 1309 replaced.add(rep[0])
1309 1310 newnodes = allsuccs - replaced
1310 1311 tmpnodes = allsuccs & replaced
1311 1312 return newnodes, tmpnodes
1312 1313
1313 1314
1314 1315 def processreplacement(state):
1315 1316 """process the list of replacements to return
1316 1317
1317 1318 1) the final mapping between original and created nodes
1318 1319 2) the list of temporary node created by histedit
1319 1320 3) the list of new commit created by histedit"""
1320 1321 replacements = state.replacements
1321 1322 allsuccs = set()
1322 1323 replaced = set()
1323 1324 fullmapping = {}
1324 1325 # initialize basic set
1325 1326 # fullmapping records all operations recorded in replacement
1326 1327 for rep in replacements:
1327 1328 allsuccs.update(rep[1])
1328 1329 replaced.add(rep[0])
1329 1330 fullmapping.setdefault(rep[0], set()).update(rep[1])
1330 1331 new = allsuccs - replaced
1331 1332 tmpnodes = allsuccs & replaced
1332 1333 # Reduce content fullmapping into direct relation between original nodes
1333 1334 # and final node created during history edition
1334 1335 # Dropped changeset are replaced by an empty list
1335 1336 toproceed = set(fullmapping)
1336 1337 final = {}
1337 1338 while toproceed:
1338 1339 for x in list(toproceed):
1339 1340 succs = fullmapping[x]
1340 1341 for s in list(succs):
1341 1342 if s in toproceed:
1342 1343 # non final node with unknown closure
1343 1344 # We can't process this now
1344 1345 break
1345 1346 elif s in final:
1346 1347 # non final node, replace with closure
1347 1348 succs.remove(s)
1348 1349 succs.update(final[s])
1349 1350 else:
1350 1351 final[x] = succs
1351 1352 toproceed.remove(x)
1352 1353 # remove tmpnodes from final mapping
1353 1354 for n in tmpnodes:
1354 1355 del final[n]
1355 1356 # we expect all changes involved in final to exist in the repo
1356 1357 # turn `final` into list (topologically sorted)
1357 1358 nm = state.repo.changelog.nodemap
1358 1359 for prec, succs in final.items():
1359 1360 final[prec] = sorted(succs, key=nm.get)
1360 1361
1361 1362 # computed topmost element (necessary for bookmark)
1362 1363 if new:
1363 1364 newtopmost = sorted(new, key=state.repo.changelog.rev)[-1]
1364 1365 elif not final:
1365 1366 # Nothing rewritten at all. we won't need `newtopmost`
1366 1367 # It is the same as `oldtopmost` and `processreplacement` know it
1367 1368 newtopmost = None
1368 1369 else:
1369 1370 # every body died. The newtopmost is the parent of the root.
1370 1371 r = state.repo.changelog.rev
1371 1372 newtopmost = state.repo[sorted(final, key=r)[0]].p1().node()
1372 1373
1373 1374 return final, tmpnodes, new, newtopmost
1374 1375
1375 1376 def movebookmarks(ui, repo, mapping, oldtopmost, newtopmost):
1376 1377 """Move bookmark from old to newly created node"""
1377 1378 if not mapping:
1378 1379 # if nothing got rewritten there is not purpose for this function
1379 1380 return
1380 1381 moves = []
1381 1382 for bk, old in sorted(repo._bookmarks.iteritems()):
1382 1383 if old == oldtopmost:
1383 1384 # special case ensure bookmark stay on tip.
1384 1385 #
1385 1386 # This is arguably a feature and we may only want that for the
1386 1387 # active bookmark. But the behavior is kept compatible with the old
1387 1388 # version for now.
1388 1389 moves.append((bk, newtopmost))
1389 1390 continue
1390 1391 base = old
1391 1392 new = mapping.get(base, None)
1392 1393 if new is None:
1393 1394 continue
1394 1395 while not new:
1395 1396 # base is killed, trying with parent
1396 1397 base = repo[base].p1().node()
1397 1398 new = mapping.get(base, (base,))
1398 1399 # nothing to move
1399 1400 moves.append((bk, new[-1]))
1400 1401 if moves:
1401 1402 lock = tr = None
1402 1403 try:
1403 1404 lock = repo.lock()
1404 1405 tr = repo.transaction('histedit')
1405 1406 marks = repo._bookmarks
1406 1407 for mark, new in moves:
1407 1408 old = marks[mark]
1408 1409 ui.note(_('histedit: moving bookmarks %s from %s to %s\n')
1409 1410 % (mark, node.short(old), node.short(new)))
1410 1411 marks[mark] = new
1411 1412 marks.recordchange(tr)
1412 1413 tr.close()
1413 1414 finally:
1414 1415 release(tr, lock)
1415 1416
1416 1417 def cleanupnode(ui, repo, name, nodes):
1417 1418 """strip a group of nodes from the repository
1418 1419
1419 1420 The set of node to strip may contains unknown nodes."""
1420 1421 ui.debug('should strip %s nodes %s\n' %
1421 1422 (name, ', '.join([node.short(n) for n in nodes])))
1422 1423 lock = None
1423 1424 try:
1424 1425 lock = repo.lock()
1425 1426 # do not let filtering get in the way of the cleanse
1426 1427 # we should probably get rid of obsolescence marker created during the
1427 1428 # histedit, but we currently do not have such information.
1428 1429 repo = repo.unfiltered()
1429 1430 # Find all nodes that need to be stripped
1430 1431 # (we use %lr instead of %ln to silently ignore unknown items)
1431 1432 nm = repo.changelog.nodemap
1432 1433 nodes = sorted(n for n in nodes if n in nm)
1433 1434 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
1434 1435 for c in roots:
1435 1436 # We should process node in reverse order to strip tip most first.
1436 1437 # but this trigger a bug in changegroup hook.
1437 1438 # This would reduce bundle overhead
1438 1439 repair.strip(ui, repo, c)
1439 1440 finally:
1440 1441 release(lock)
1441 1442
1442 1443 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
1443 1444 if isinstance(nodelist, str):
1444 1445 nodelist = [nodelist]
1445 1446 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
1446 1447 state = histeditstate(repo)
1447 1448 state.read()
1448 1449 histedit_nodes = set([action.nodetoverify() for action
1449 1450 in state.actions if action.nodetoverify()])
1450 1451 strip_nodes = set([repo[n].node() for n in nodelist])
1451 1452 common_nodes = histedit_nodes & strip_nodes
1452 1453 if common_nodes:
1453 1454 raise error.Abort(_("histedit in progress, can't strip %s")
1454 1455 % ', '.join(node.short(x) for x in common_nodes))
1455 1456 return orig(ui, repo, nodelist, *args, **kwargs)
1456 1457
1457 1458 extensions.wrapfunction(repair, 'strip', stripwrapper)
1458 1459
1459 1460 def summaryhook(ui, repo):
1460 1461 if not os.path.exists(repo.join('histedit-state')):
1461 1462 return
1462 1463 state = histeditstate(repo)
1463 1464 state.read()
1464 1465 if state.actions:
1465 1466 # i18n: column positioning for "hg summary"
1466 1467 ui.write(_('hist: %s (histedit --continue)\n') %
1467 1468 (ui.label(_('%d remaining'), 'histedit.remaining') %
1468 1469 len(state.actions)))
1469 1470
1470 1471 def extsetup(ui):
1471 1472 cmdutil.summaryhooks.add('histedit', summaryhook)
1472 1473 cmdutil.unfinishedstates.append(
1473 1474 ['histedit-state', False, True, _('histedit in progress'),
1474 1475 _("use 'hg histedit --continue' or 'hg histedit --abort'")])
1475 1476 cmdutil.afterresolvedstates.append(
1476 1477 ['histedit-state', _('hg histedit --continue')])
1477 1478 if ui.configbool("experimental", "histeditng"):
1478 1479 globals()['base'] = addhisteditaction(['base', 'b'])(base)
@@ -1,448 +1,450 b''
1 1 Test argument handling and various data parsing
2 2 ==================================================
3 3
4 4
5 5 Enable extensions used by this test.
6 6 $ cat >>$HGRCPATH <<EOF
7 7 > [extensions]
8 8 > histedit=
9 9 > EOF
10 10
11 11 Repo setup.
12 12 $ hg init foo
13 13 $ cd foo
14 14 $ echo alpha >> alpha
15 15 $ hg addr
16 16 adding alpha
17 17 $ hg ci -m one
18 18 $ echo alpha >> alpha
19 19 $ hg ci -m two
20 20 $ echo alpha >> alpha
21 21 $ hg ci -m three
22 22 $ echo alpha >> alpha
23 23 $ hg ci -m four
24 24 $ echo alpha >> alpha
25 25 $ hg ci -m five
26 26
27 27 $ hg log --style compact --graph
28 28 @ 4[tip] 08d98a8350f3 1970-01-01 00:00 +0000 test
29 29 | five
30 30 |
31 31 o 3 c8e68270e35a 1970-01-01 00:00 +0000 test
32 32 | four
33 33 |
34 34 o 2 eb57da33312f 1970-01-01 00:00 +0000 test
35 35 | three
36 36 |
37 37 o 1 579e40513370 1970-01-01 00:00 +0000 test
38 38 | two
39 39 |
40 40 o 0 6058cbb6cfd7 1970-01-01 00:00 +0000 test
41 41 one
42 42
43 43
44 44 histedit --continue/--abort with no existing state
45 45 --------------------------------------------------
46 46
47 47 $ hg histedit --continue
48 48 abort: no histedit in progress
49 49 [255]
50 50 $ hg histedit --abort
51 51 abort: no histedit in progress
52 52 [255]
53 53
54 54 Run a dummy edit to make sure we get tip^^ correctly via revsingle.
55 55 --------------------------------------------------------------------
56 56
57 57 $ HGEDITOR=cat hg histedit "tip^^"
58 58 pick eb57da33312f 2 three
59 59 pick c8e68270e35a 3 four
60 60 pick 08d98a8350f3 4 five
61 61
62 62 # Edit history between eb57da33312f and 08d98a8350f3
63 63 #
64 64 # Commits are listed from least to most recent
65 65 #
66 66 # Commands:
67 #
68 # e, edit = use commit, but stop for amending
69 # m, mess = edit commit message without changing commit content
67 70 # p, pick = use commit
68 # e, edit = use commit, but stop for amending
71 # d, drop = remove commit from history
69 72 # f, fold = use commit, but combine it with the one above
70 73 # r, roll = like fold, but discard this commit's description
71 # d, drop = remove commit from history
72 # m, mess = edit commit message without changing commit content
73 74 #
74 75
75 76 Run on a revision not ancestors of the current working directory.
76 77 --------------------------------------------------------------------
77 78
78 79 $ hg up 2
79 80 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
80 81 $ hg histedit -r 4
81 82 abort: 08d98a8350f3 is not an ancestor of working directory
82 83 [255]
83 84 $ hg up --quiet
84 85
85 86
86 87 Test that we pick the minimum of a revrange
87 88 ---------------------------------------
88 89
89 90 $ HGEDITOR=cat hg histedit '2::' --commands - << EOF
90 91 > pick eb57da33312f 2 three
91 92 > pick c8e68270e35a 3 four
92 93 > pick 08d98a8350f3 4 five
93 94 > EOF
94 95 $ hg up --quiet
95 96
96 97 $ HGEDITOR=cat hg histedit 'tip:2' --commands - << EOF
97 98 > pick eb57da33312f 2 three
98 99 > pick c8e68270e35a 3 four
99 100 > pick 08d98a8350f3 4 five
100 101 > EOF
101 102 $ hg up --quiet
102 103
103 104 Test config specified default
104 105 -----------------------------
105 106
106 107 $ HGEDITOR=cat hg histedit --config "histedit.defaultrev=only(.) - ::eb57da33312f" --commands - << EOF
107 108 > pick c8e68270e35a 3 four
108 109 > pick 08d98a8350f3 4 five
109 110 > EOF
110 111
111 112 Run on a revision not descendants of the initial parent
112 113 --------------------------------------------------------------------
113 114
114 115 Test the message shown for inconsistent histedit state, which may be
115 116 created (and forgotten) by Mercurial earlier than 2.7. This emulates
116 117 Mercurial earlier than 2.7 by renaming ".hg/histedit-state"
117 118 temporarily.
118 119
119 120 $ hg log -G -T '{rev} {shortest(node)} {desc}\n' -r 2::
120 121 @ 4 08d9 five
121 122 |
122 123 o 3 c8e6 four
123 124 |
124 125 o 2 eb57 three
125 126 |
126 127 $ HGEDITOR=cat hg histedit -r 4 --commands - << EOF
127 128 > edit 08d98a8350f3 4 five
128 129 > EOF
129 130 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
130 131 reverting alpha
131 132 Editing (08d98a8350f3), you may commit or record as needed now.
132 133 (hg histedit --continue to resume)
133 134 [1]
134 135
135 136 $ mv .hg/histedit-state .hg/histedit-state.back
136 137 $ hg update --quiet --clean 2
137 138 $ echo alpha >> alpha
138 139 $ mv .hg/histedit-state.back .hg/histedit-state
139 140
140 141 $ hg histedit --continue
141 142 saved backup bundle to $TESTTMP/foo/.hg/strip-backup/08d98a8350f3-02594089-backup.hg (glob)
142 143 $ hg log -G -T '{rev} {shortest(node)} {desc}\n' -r 2::
143 144 @ 4 f5ed five
144 145 |
145 146 | o 3 c8e6 four
146 147 |/
147 148 o 2 eb57 three
148 149 |
149 150
150 151 $ hg unbundle -q $TESTTMP/foo/.hg/strip-backup/08d98a8350f3-02594089-backup.hg
151 152 $ hg strip -q -r f5ed --config extensions.strip=
152 153 $ hg up -q 08d98a8350f3
153 154
154 155 Test that missing revisions are detected
155 156 ---------------------------------------
156 157
157 158 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
158 159 > pick eb57da33312f 2 three
159 160 > pick 08d98a8350f3 4 five
160 161 > EOF
161 162 hg: parse error: missing rules for changeset c8e68270e35a
162 163 (use "drop c8e68270e35a" to discard, see also: "hg help -e histedit.config")
163 164 [255]
164 165
165 166 Test that extra revisions are detected
166 167 ---------------------------------------
167 168
168 169 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
169 170 > pick 6058cbb6cfd7 0 one
170 171 > pick c8e68270e35a 3 four
171 172 > pick 08d98a8350f3 4 five
172 173 > EOF
173 174 hg: parse error: may not use "pick" with changesets other than the ones listed
174 175 [255]
175 176
176 177 Test malformed line
177 178 ---------------------------------------
178 179
179 180 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
180 181 > pickeb57da33312f2three
181 182 > pick c8e68270e35a 3 four
182 183 > pick 08d98a8350f3 4 five
183 184 > EOF
184 185 hg: parse error: malformed line "pickeb57da33312f2three"
185 186 [255]
186 187
187 188 Test unknown changeset
188 189 ---------------------------------------
189 190
190 191 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
191 192 > pick 0123456789ab 2 three
192 193 > pick c8e68270e35a 3 four
193 194 > pick 08d98a8350f3 4 five
194 195 > EOF
195 196 hg: parse error: unknown changeset 0123456789ab listed
196 197 [255]
197 198
198 199 Test unknown command
199 200 ---------------------------------------
200 201
201 202 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
202 203 > coin eb57da33312f 2 three
203 204 > pick c8e68270e35a 3 four
204 205 > pick 08d98a8350f3 4 five
205 206 > EOF
206 207 hg: parse error: unknown action "coin"
207 208 [255]
208 209
209 210 Test duplicated changeset
210 211 ---------------------------------------
211 212
212 213 So one is missing and one appear twice.
213 214
214 215 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
215 216 > pick eb57da33312f 2 three
216 217 > pick eb57da33312f 2 three
217 218 > pick 08d98a8350f3 4 five
218 219 > EOF
219 220 hg: parse error: duplicated command for changeset eb57da33312f
220 221 [255]
221 222
222 223 Test bogus rev
223 224 ---------------------------------------
224 225
225 226 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
226 227 > pick eb57da33312f 2 three
227 228 > pick 0
228 229 > pick 08d98a8350f3 4 five
229 230 > EOF
230 231 hg: parse error: invalid changeset 0
231 232 [255]
232 233
233 234 Test short version of command
234 235 ---------------------------------------
235 236
236 237 Note: we use varying amounts of white space between command name and changeset
237 238 short hash. This tests issue3893.
238 239
239 240 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
240 241 > pick eb57da33312f 2 three
241 242 > p c8e68270e35a 3 four
242 243 > f 08d98a8350f3 4 five
243 244 > EOF
244 245 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
245 246 reverting alpha
246 247 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
247 248 four
248 249 ***
249 250 five
250 251
251 252
252 253
253 254 HG: Enter commit message. Lines beginning with 'HG:' are removed.
254 255 HG: Leave message empty to abort commit.
255 256 HG: --
256 257 HG: user: test
257 258 HG: branch 'default'
258 259 HG: changed alpha
259 260 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
260 261 saved backup bundle to $TESTTMP/foo/.hg/strip-backup/*-backup.hg (glob)
261 262 saved backup bundle to $TESTTMP/foo/.hg/strip-backup/*-backup.hg (glob)
262 263
263 264 $ hg update -q 2
264 265 $ echo x > x
265 266 $ hg add x
266 267 $ hg commit -m'x' x
267 268 created new head
268 269 $ hg histedit -r 'heads(all())'
269 270 abort: The specified revisions must have exactly one common root
270 271 [255]
271 272
272 273 Test that trimming description using multi-byte characters
273 274 --------------------------------------------------------------------
274 275
275 276 $ python <<EOF
276 277 > fp = open('logfile', 'w')
277 278 > fp.write('12345678901234567890123456789012345678901234567890' +
278 279 > '12345') # there are 5 more columns for 80 columns
279 280 >
280 281 > # 2 x 4 = 8 columns, but 3 x 4 = 12 bytes
281 282 > fp.write(u'\u3042\u3044\u3046\u3048'.encode('utf-8'))
282 283 >
283 284 > fp.close()
284 285 > EOF
285 286 $ echo xx >> x
286 287 $ hg --encoding utf-8 commit --logfile logfile
287 288
288 289 $ HGEDITOR=cat hg --encoding utf-8 histedit tip
289 290 pick 3d3ea1f3a10b 5 1234567890123456789012345678901234567890123456789012345\xe3\x81\x82... (esc)
290 291
291 292 # Edit history between 3d3ea1f3a10b and 3d3ea1f3a10b
292 293 #
293 294 # Commits are listed from least to most recent
294 295 #
295 296 # Commands:
297 #
298 # e, edit = use commit, but stop for amending
299 # m, mess = edit commit message without changing commit content
296 300 # p, pick = use commit
297 # e, edit = use commit, but stop for amending
301 # d, drop = remove commit from history
298 302 # f, fold = use commit, but combine it with the one above
299 303 # r, roll = like fold, but discard this commit's description
300 # d, drop = remove commit from history
301 # m, mess = edit commit message without changing commit content
302 304 #
303 305
304 306 Test --continue with --keep
305 307
306 308 $ hg strip -q -r . --config extensions.strip=
307 309 $ hg histedit '.^' -q --keep --commands - << EOF
308 310 > edit eb57da33312f 2 three
309 311 > pick f3cfcca30c44 4 x
310 312 > EOF
311 313 Editing (eb57da33312f), you may commit or record as needed now.
312 314 (hg histedit --continue to resume)
313 315 [1]
314 316 $ echo edit >> alpha
315 317 $ hg histedit -q --continue
316 318 $ hg log -G -T '{rev}:{node|short} {desc}'
317 319 @ 6:8fda0c726bf2 x
318 320 |
319 321 o 5:63379946892c three
320 322 |
321 323 | o 4:f3cfcca30c44 x
322 324 | |
323 325 | | o 3:2a30f3cfee78 four
324 326 | |/ ***
325 327 | | five
326 328 | o 2:eb57da33312f three
327 329 |/
328 330 o 1:579e40513370 two
329 331 |
330 332 o 0:6058cbb6cfd7 one
331 333
332 334
333 335 Test that abort fails gracefully on exception
334 336 ----------------------------------------------
335 337 $ hg histedit . -q --commands - << EOF
336 338 > edit 8fda0c726bf2 6 x
337 339 > EOF
338 340 Editing (8fda0c726bf2), you may commit or record as needed now.
339 341 (hg histedit --continue to resume)
340 342 [1]
341 343 Corrupt histedit state file
342 344 $ sed 's/8fda0c726bf2/123456789012/' .hg/histedit-state > ../corrupt-histedit
343 345 $ mv ../corrupt-histedit .hg/histedit-state
344 346 $ hg histedit --abort
345 347 warning: encountered an exception during histedit --abort; the repository may not have been completely cleaned up
346 348 abort: .*(No such file or directory:|The system cannot find the file specified).* (re)
347 349 [255]
348 350 Histedit state has been exited
349 351 $ hg summary -q
350 352 parent: 5:63379946892c
351 353 commit: 1 added, 1 unknown (new branch head)
352 354 update: 4 new changesets (update)
353 355
354 356 $ cd ..
355 357
356 358 Set up default base revision tests
357 359
358 360 $ hg init defaultbase
359 361 $ cd defaultbase
360 362 $ touch foo
361 363 $ hg -q commit -A -m root
362 364 $ echo 1 > foo
363 365 $ hg commit -m 'public 1'
364 366 $ hg phase --force --public -r .
365 367 $ echo 2 > foo
366 368 $ hg commit -m 'draft after public'
367 369 $ hg -q up -r 1
368 370 $ echo 3 > foo
369 371 $ hg commit -m 'head 1 public'
370 372 created new head
371 373 $ hg phase --force --public -r .
372 374 $ echo 4 > foo
373 375 $ hg commit -m 'head 1 draft 1'
374 376 $ echo 5 > foo
375 377 $ hg commit -m 'head 1 draft 2'
376 378 $ hg -q up -r 2
377 379 $ echo 6 > foo
378 380 $ hg commit -m 'head 2 commit 1'
379 381 $ echo 7 > foo
380 382 $ hg commit -m 'head 2 commit 2'
381 383 $ hg -q up -r 2
382 384 $ echo 8 > foo
383 385 $ hg commit -m 'head 3'
384 386 created new head
385 387 $ hg -q up -r 2
386 388 $ echo 9 > foo
387 389 $ hg commit -m 'head 4'
388 390 created new head
389 391 $ hg merge --tool :local -r 8
390 392 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
391 393 (branch merge, don't forget to commit)
392 394 $ hg commit -m 'merge head 3 into head 4'
393 395 $ echo 11 > foo
394 396 $ hg commit -m 'commit 1 after merge'
395 397 $ echo 12 > foo
396 398 $ hg commit -m 'commit 2 after merge'
397 399
398 400 $ hg log -G -T '{rev}:{node|short} {phase} {desc}\n'
399 401 @ 12:8cde254db839 draft commit 2 after merge
400 402 |
401 403 o 11:6f2f0241f119 draft commit 1 after merge
402 404 |
403 405 o 10:90506cc76b00 draft merge head 3 into head 4
404 406 |\
405 407 | o 9:f8607a373a97 draft head 4
406 408 | |
407 409 o | 8:0da92be05148 draft head 3
408 410 |/
409 411 | o 7:4c35cdf97d5e draft head 2 commit 2
410 412 | |
411 413 | o 6:931820154288 draft head 2 commit 1
412 414 |/
413 415 | o 5:8cdc02b9bc63 draft head 1 draft 2
414 416 | |
415 417 | o 4:463b8c0d2973 draft head 1 draft 1
416 418 | |
417 419 | o 3:23a0c4eefcbf public head 1 public
418 420 | |
419 421 o | 2:4117331c3abb draft draft after public
420 422 |/
421 423 o 1:4426d359ea59 public public 1
422 424 |
423 425 o 0:54136a8ddf32 public root
424 426
425 427
426 428 Default base revision should stop at public changesets
427 429
428 430 $ hg -q up 8cdc02b9bc63
429 431 $ hg histedit --commands - <<EOF
430 432 > pick 463b8c0d2973
431 433 > pick 8cdc02b9bc63
432 434 > EOF
433 435
434 436 Default base revision should stop at branchpoint
435 437
436 438 $ hg -q up 4c35cdf97d5e
437 439 $ hg histedit --commands - <<EOF
438 440 > pick 931820154288
439 441 > pick 4c35cdf97d5e
440 442 > EOF
441 443
442 444 Default base revision should stop at merge commit
443 445
444 446 $ hg -q up 8cde254db839
445 447 $ hg histedit --commands - <<EOF
446 448 > pick 6f2f0241f119
447 449 > pick 8cde254db839
448 450 > EOF
@@ -1,180 +1,182 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 $ hg init r
9 9 $ cd r
10 10
11 11 $ for x in a b c d e f ; do
12 12 > echo $x > $x
13 13 > hg add $x
14 14 > hg ci -m $x
15 15 > done
16 16
17 17 $ hg book -r 1 will-move-backwards
18 18 $ hg book -r 2 two
19 19 $ hg book -r 2 also-two
20 20 $ hg book -r 3 three
21 21 $ hg book -r 4 four
22 22 $ hg book -r tip five
23 23 $ hg log --graph
24 24 @ changeset: 5:652413bf663e
25 25 | bookmark: five
26 26 | tag: tip
27 27 | user: test
28 28 | date: Thu Jan 01 00:00:00 1970 +0000
29 29 | summary: f
30 30 |
31 31 o changeset: 4:e860deea161a
32 32 | bookmark: four
33 33 | user: test
34 34 | date: Thu Jan 01 00:00:00 1970 +0000
35 35 | summary: e
36 36 |
37 37 o changeset: 3:055a42cdd887
38 38 | bookmark: three
39 39 | user: test
40 40 | date: Thu Jan 01 00:00:00 1970 +0000
41 41 | summary: d
42 42 |
43 43 o changeset: 2:177f92b77385
44 44 | bookmark: also-two
45 45 | bookmark: two
46 46 | user: test
47 47 | date: Thu Jan 01 00:00:00 1970 +0000
48 48 | summary: c
49 49 |
50 50 o changeset: 1:d2ae7f538514
51 51 | bookmark: will-move-backwards
52 52 | user: test
53 53 | date: Thu Jan 01 00:00:00 1970 +0000
54 54 | summary: b
55 55 |
56 56 o changeset: 0:cb9a9f314b8b
57 57 user: test
58 58 date: Thu Jan 01 00:00:00 1970 +0000
59 59 summary: a
60 60
61 61 $ HGEDITOR=cat hg histedit 1
62 62 pick d2ae7f538514 1 b
63 63 pick 177f92b77385 2 c
64 64 pick 055a42cdd887 3 d
65 65 pick e860deea161a 4 e
66 66 pick 652413bf663e 5 f
67 67
68 68 # Edit history between d2ae7f538514 and 652413bf663e
69 69 #
70 70 # Commits are listed from least to most recent
71 71 #
72 72 # Commands:
73 #
74 # e, edit = use commit, but stop for amending
75 # m, mess = edit commit message without changing commit content
73 76 # p, pick = use commit
74 # e, edit = use commit, but stop for amending
77 # d, drop = remove commit from history
75 78 # f, fold = use commit, but combine it with the one above
76 79 # r, roll = like fold, but discard this commit's description
77 # d, drop = remove commit from history
78 # m, mess = edit commit message without changing commit content
79 80 #
80 81 $ hg histedit 1 --commands - --verbose << EOF | grep histedit
81 82 > pick 177f92b77385 2 c
82 83 > drop d2ae7f538514 1 b
83 84 > pick 055a42cdd887 3 d
84 85 > fold e860deea161a 4 e
85 86 > pick 652413bf663e 5 f
86 87 > EOF
87 88 saved backup bundle to $TESTTMP/r/.hg/strip-backup/96e494a2d553-3c6c5d92-backup.hg (glob)
88 89 histedit: moving bookmarks also-two from 177f92b77385 to b346ab9a313d
89 90 histedit: moving bookmarks five from 652413bf663e to cacdfd884a93
90 91 histedit: moving bookmarks four from e860deea161a to 59d9f330561f
91 92 histedit: moving bookmarks three from 055a42cdd887 to 59d9f330561f
92 93 histedit: moving bookmarks two from 177f92b77385 to b346ab9a313d
93 94 histedit: moving bookmarks will-move-backwards from d2ae7f538514 to cb9a9f314b8b
94 95 saved backup bundle to $TESTTMP/r/.hg/strip-backup/d2ae7f538514-48787b8d-backup.hg (glob)
95 96 $ hg log --graph
96 97 @ changeset: 3:cacdfd884a93
97 98 | bookmark: five
98 99 | tag: tip
99 100 | user: test
100 101 | date: Thu Jan 01 00:00:00 1970 +0000
101 102 | summary: f
102 103 |
103 104 o changeset: 2:59d9f330561f
104 105 | bookmark: four
105 106 | bookmark: three
106 107 | user: test
107 108 | date: Thu Jan 01 00:00:00 1970 +0000
108 109 | summary: d
109 110 |
110 111 o changeset: 1:b346ab9a313d
111 112 | bookmark: also-two
112 113 | bookmark: two
113 114 | user: test
114 115 | date: Thu Jan 01 00:00:00 1970 +0000
115 116 | summary: c
116 117 |
117 118 o changeset: 0:cb9a9f314b8b
118 119 bookmark: will-move-backwards
119 120 user: test
120 121 date: Thu Jan 01 00:00:00 1970 +0000
121 122 summary: a
122 123
123 124 $ HGEDITOR=cat hg histedit 1
124 125 pick b346ab9a313d 1 c
125 126 pick 59d9f330561f 2 d
126 127 pick cacdfd884a93 3 f
127 128
128 129 # Edit history between b346ab9a313d and cacdfd884a93
129 130 #
130 131 # Commits are listed from least to most recent
131 132 #
132 133 # Commands:
134 #
135 # e, edit = use commit, but stop for amending
136 # m, mess = edit commit message without changing commit content
133 137 # p, pick = use commit
134 # e, edit = use commit, but stop for amending
138 # d, drop = remove commit from history
135 139 # f, fold = use commit, but combine it with the one above
136 140 # r, roll = like fold, but discard this commit's description
137 # d, drop = remove commit from history
138 # m, mess = edit commit message without changing commit content
139 141 #
140 142 $ hg histedit 1 --commands - --verbose << EOF | grep histedit
141 143 > pick b346ab9a313d 1 c
142 144 > pick cacdfd884a93 3 f
143 145 > pick 59d9f330561f 2 d
144 146 > EOF
145 147 histedit: moving bookmarks five from cacdfd884a93 to c04e50810e4b
146 148 histedit: moving bookmarks four from 59d9f330561f to c04e50810e4b
147 149 histedit: moving bookmarks three from 59d9f330561f to c04e50810e4b
148 150 saved backup bundle to $TESTTMP/r/.hg/strip-backup/59d9f330561f-073008af-backup.hg (glob)
149 151
150 152 We expect 'five' to stay at tip, since the tipmost bookmark is most
151 153 likely the useful signal.
152 154
153 155 $ hg log --graph
154 156 @ changeset: 3:c04e50810e4b
155 157 | bookmark: five
156 158 | bookmark: four
157 159 | bookmark: three
158 160 | tag: tip
159 161 | user: test
160 162 | date: Thu Jan 01 00:00:00 1970 +0000
161 163 | summary: d
162 164 |
163 165 o changeset: 2:c13eb81022ca
164 166 | user: test
165 167 | date: Thu Jan 01 00:00:00 1970 +0000
166 168 | summary: f
167 169 |
168 170 o changeset: 1:b346ab9a313d
169 171 | bookmark: also-two
170 172 | bookmark: two
171 173 | user: test
172 174 | date: Thu Jan 01 00:00:00 1970 +0000
173 175 | summary: c
174 176 |
175 177 o changeset: 0:cb9a9f314b8b
176 178 bookmark: will-move-backwards
177 179 user: test
178 180 date: Thu Jan 01 00:00:00 1970 +0000
179 181 summary: a
180 182
@@ -1,456 +1,458 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 r
11 11 > cd r
12 12 > for x in a b c d e f ; do
13 13 > echo $x > $x
14 14 > hg add $x
15 15 > hg ci -m $x
16 16 > done
17 17 > }
18 18
19 19 $ initrepo
20 20
21 21 log before edit
22 22 $ hg log --graph
23 23 @ changeset: 5:652413bf663e
24 24 | tag: tip
25 25 | user: test
26 26 | date: Thu Jan 01 00:00:00 1970 +0000
27 27 | summary: f
28 28 |
29 29 o changeset: 4:e860deea161a
30 30 | user: test
31 31 | date: Thu Jan 01 00:00:00 1970 +0000
32 32 | summary: e
33 33 |
34 34 o changeset: 3:055a42cdd887
35 35 | user: test
36 36 | date: Thu Jan 01 00:00:00 1970 +0000
37 37 | summary: d
38 38 |
39 39 o changeset: 2:177f92b77385
40 40 | user: test
41 41 | date: Thu Jan 01 00:00:00 1970 +0000
42 42 | summary: c
43 43 |
44 44 o changeset: 1:d2ae7f538514
45 45 | user: test
46 46 | date: Thu Jan 01 00:00:00 1970 +0000
47 47 | summary: b
48 48 |
49 49 o changeset: 0:cb9a9f314b8b
50 50 user: test
51 51 date: Thu Jan 01 00:00:00 1970 +0000
52 52 summary: a
53 53
54 54
55 55 show the edit commands offered
56 56 $ HGEDITOR=cat hg histedit 177f92b77385
57 57 pick 177f92b77385 2 c
58 58 pick 055a42cdd887 3 d
59 59 pick e860deea161a 4 e
60 60 pick 652413bf663e 5 f
61 61
62 62 # Edit history between 177f92b77385 and 652413bf663e
63 63 #
64 64 # Commits are listed from least to most recent
65 65 #
66 66 # Commands:
67 #
68 # e, edit = use commit, but stop for amending
69 # m, mess = edit commit message without changing commit content
67 70 # p, pick = use commit
68 # e, edit = use commit, but stop for amending
71 # d, drop = remove commit from history
69 72 # f, fold = use commit, but combine it with the one above
70 73 # r, roll = like fold, but discard this commit's description
71 # d, drop = remove commit from history
72 # m, mess = edit commit message without changing commit content
73 74 #
74 75
75 76 edit the history
76 77 (use a hacky editor to check histedit-last-edit.txt backup)
77 78
78 79 $ EDITED="$TESTTMP/editedhistory"
79 80 $ cat > $EDITED <<EOF
80 81 > edit 177f92b77385 c
81 82 > pick e860deea161a e
82 83 > pick 652413bf663e f
83 84 > pick 055a42cdd887 d
84 85 > EOF
85 86 $ HGEDITOR="cat \"$EDITED\" > " hg histedit 177f92b77385 2>&1 | fixbundle
86 87 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
87 88 Editing (177f92b77385), you may commit or record as needed now.
88 89 (hg histedit --continue to resume)
89 90
90 91 rules should end up in .hg/histedit-last-edit.txt:
91 92 $ cat .hg/histedit-last-edit.txt
92 93 edit 177f92b77385 c
93 94 pick e860deea161a e
94 95 pick 652413bf663e f
95 96 pick 055a42cdd887 d
96 97
97 98 $ hg histedit --abort
98 99 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
99 100 $ cat > $EDITED <<EOF
100 101 > pick 177f92b77385 c
101 102 > pick e860deea161a e
102 103 > pick 652413bf663e f
103 104 > pick 055a42cdd887 d
104 105 > EOF
105 106 $ HGEDITOR="cat \"$EDITED\" > " hg histedit 177f92b77385 2>&1 | fixbundle
106 107 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
107 108
108 109 log after edit
109 110 $ hg log --graph
110 111 @ changeset: 5:07114f51870f
111 112 | tag: tip
112 113 | user: test
113 114 | date: Thu Jan 01 00:00:00 1970 +0000
114 115 | summary: d
115 116 |
116 117 o changeset: 4:8ade9693061e
117 118 | user: test
118 119 | date: Thu Jan 01 00:00:00 1970 +0000
119 120 | summary: f
120 121 |
121 122 o changeset: 3:d8249471110a
122 123 | user: test
123 124 | date: Thu Jan 01 00:00:00 1970 +0000
124 125 | summary: e
125 126 |
126 127 o changeset: 2:177f92b77385
127 128 | user: test
128 129 | date: Thu Jan 01 00:00:00 1970 +0000
129 130 | summary: c
130 131 |
131 132 o changeset: 1:d2ae7f538514
132 133 | user: test
133 134 | date: Thu Jan 01 00:00:00 1970 +0000
134 135 | summary: b
135 136 |
136 137 o changeset: 0:cb9a9f314b8b
137 138 user: test
138 139 date: Thu Jan 01 00:00:00 1970 +0000
139 140 summary: a
140 141
141 142
142 143 put things back
143 144
144 145 $ hg histedit 177f92b77385 --commands - 2>&1 << EOF | fixbundle
145 146 > pick 177f92b77385 c
146 147 > pick 07114f51870f d
147 148 > pick d8249471110a e
148 149 > pick 8ade9693061e f
149 150 > EOF
150 151 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
151 152
152 153 $ hg log --graph
153 154 @ changeset: 5:7eca9b5b1148
154 155 | tag: tip
155 156 | user: test
156 157 | date: Thu Jan 01 00:00:00 1970 +0000
157 158 | summary: f
158 159 |
159 160 o changeset: 4:915da888f2de
160 161 | user: test
161 162 | date: Thu Jan 01 00:00:00 1970 +0000
162 163 | summary: e
163 164 |
164 165 o changeset: 3:10517e47bbbb
165 166 | user: test
166 167 | date: Thu Jan 01 00:00:00 1970 +0000
167 168 | summary: d
168 169 |
169 170 o changeset: 2:177f92b77385
170 171 | user: test
171 172 | date: Thu Jan 01 00:00:00 1970 +0000
172 173 | summary: c
173 174 |
174 175 o changeset: 1:d2ae7f538514
175 176 | user: test
176 177 | date: Thu Jan 01 00:00:00 1970 +0000
177 178 | summary: b
178 179 |
179 180 o changeset: 0:cb9a9f314b8b
180 181 user: test
181 182 date: Thu Jan 01 00:00:00 1970 +0000
182 183 summary: a
183 184
184 185
185 186 slightly different this time
186 187
187 188 $ hg histedit 177f92b77385 --commands - << EOF 2>&1 | fixbundle
188 189 > pick 10517e47bbbb d
189 190 > pick 7eca9b5b1148 f
190 191 > pick 915da888f2de e
191 192 > pick 177f92b77385 c
192 193 > EOF
193 194 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
194 195 $ hg log --graph
195 196 @ changeset: 5:38b92f448761
196 197 | tag: tip
197 198 | user: test
198 199 | date: Thu Jan 01 00:00:00 1970 +0000
199 200 | summary: c
200 201 |
201 202 o changeset: 4:de71b079d9ce
202 203 | user: test
203 204 | date: Thu Jan 01 00:00:00 1970 +0000
204 205 | summary: e
205 206 |
206 207 o changeset: 3:be9ae3a309c6
207 208 | user: test
208 209 | date: Thu Jan 01 00:00:00 1970 +0000
209 210 | summary: f
210 211 |
211 212 o changeset: 2:799205341b6b
212 213 | user: test
213 214 | date: Thu Jan 01 00:00:00 1970 +0000
214 215 | summary: d
215 216 |
216 217 o changeset: 1:d2ae7f538514
217 218 | user: test
218 219 | date: Thu Jan 01 00:00:00 1970 +0000
219 220 | summary: b
220 221 |
221 222 o changeset: 0:cb9a9f314b8b
222 223 user: test
223 224 date: Thu Jan 01 00:00:00 1970 +0000
224 225 summary: a
225 226
226 227
227 228 keep prevents stripping dead revs
228 229 $ hg histedit 799205341b6b --keep --commands - 2>&1 << EOF | fixbundle
229 230 > pick 799205341b6b d
230 231 > pick be9ae3a309c6 f
231 232 > pick 38b92f448761 c
232 233 > pick de71b079d9ce e
233 234 > EOF
234 235 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
235 236 $ hg log --graph
236 237 @ changeset: 7:803ef1c6fcfd
237 238 | tag: tip
238 239 | user: test
239 240 | date: Thu Jan 01 00:00:00 1970 +0000
240 241 | summary: e
241 242 |
242 243 o changeset: 6:ece0b8d93dda
243 244 | parent: 3:be9ae3a309c6
244 245 | user: test
245 246 | date: Thu Jan 01 00:00:00 1970 +0000
246 247 | summary: c
247 248 |
248 249 | o changeset: 5:38b92f448761
249 250 | | user: test
250 251 | | date: Thu Jan 01 00:00:00 1970 +0000
251 252 | | summary: c
252 253 | |
253 254 | o changeset: 4:de71b079d9ce
254 255 |/ user: test
255 256 | date: Thu Jan 01 00:00:00 1970 +0000
256 257 | summary: e
257 258 |
258 259 o changeset: 3:be9ae3a309c6
259 260 | user: test
260 261 | date: Thu Jan 01 00:00:00 1970 +0000
261 262 | summary: f
262 263 |
263 264 o changeset: 2:799205341b6b
264 265 | user: test
265 266 | date: Thu Jan 01 00:00:00 1970 +0000
266 267 | summary: d
267 268 |
268 269 o changeset: 1:d2ae7f538514
269 270 | user: test
270 271 | date: Thu Jan 01 00:00:00 1970 +0000
271 272 | summary: b
272 273 |
273 274 o changeset: 0:cb9a9f314b8b
274 275 user: test
275 276 date: Thu Jan 01 00:00:00 1970 +0000
276 277 summary: a
277 278
278 279
279 280 try with --rev
280 281 $ hg histedit --commands - --rev -2 2>&1 <<EOF | fixbundle
281 282 > pick de71b079d9ce e
282 283 > pick 38b92f448761 c
283 284 > EOF
284 285 hg: parse error: may not use "pick" with changesets other than the ones listed
285 286 $ hg log --graph
286 287 @ changeset: 7:803ef1c6fcfd
287 288 | tag: tip
288 289 | user: test
289 290 | date: Thu Jan 01 00:00:00 1970 +0000
290 291 | summary: e
291 292 |
292 293 o changeset: 6:ece0b8d93dda
293 294 | parent: 3:be9ae3a309c6
294 295 | user: test
295 296 | date: Thu Jan 01 00:00:00 1970 +0000
296 297 | summary: c
297 298 |
298 299 | o changeset: 5:38b92f448761
299 300 | | user: test
300 301 | | date: Thu Jan 01 00:00:00 1970 +0000
301 302 | | summary: c
302 303 | |
303 304 | o changeset: 4:de71b079d9ce
304 305 |/ user: test
305 306 | date: Thu Jan 01 00:00:00 1970 +0000
306 307 | summary: e
307 308 |
308 309 o changeset: 3:be9ae3a309c6
309 310 | user: test
310 311 | date: Thu Jan 01 00:00:00 1970 +0000
311 312 | summary: f
312 313 |
313 314 o changeset: 2:799205341b6b
314 315 | user: test
315 316 | date: Thu Jan 01 00:00:00 1970 +0000
316 317 | summary: d
317 318 |
318 319 o changeset: 1:d2ae7f538514
319 320 | user: test
320 321 | date: Thu Jan 01 00:00:00 1970 +0000
321 322 | summary: b
322 323 |
323 324 o changeset: 0:cb9a9f314b8b
324 325 user: test
325 326 date: Thu Jan 01 00:00:00 1970 +0000
326 327 summary: a
327 328
328 329 Verify that revsetalias entries work with histedit:
329 330 $ cat >> $HGRCPATH <<EOF
330 331 > [revsetalias]
331 332 > grandparent(ARG) = p1(p1(ARG))
332 333 > EOF
333 334 $ echo extra commit >> c
334 335 $ hg ci -m 'extra commit to c'
335 336 $ HGEDITOR=cat hg histedit 'grandparent(.)'
336 337 pick ece0b8d93dda 6 c
337 338 pick 803ef1c6fcfd 7 e
338 339 pick 9c863c565126 8 extra commit to c
339 340
340 341 # Edit history between ece0b8d93dda and 9c863c565126
341 342 #
342 343 # Commits are listed from least to most recent
343 344 #
344 345 # Commands:
346 #
347 # e, edit = use commit, but stop for amending
348 # m, mess = edit commit message without changing commit content
345 349 # p, pick = use commit
346 # e, edit = use commit, but stop for amending
350 # d, drop = remove commit from history
347 351 # f, fold = use commit, but combine it with the one above
348 352 # r, roll = like fold, but discard this commit's description
349 # d, drop = remove commit from history
350 # m, mess = edit commit message without changing commit content
351 353 #
352 354
353 355 should also work if a commit message is missing
354 356 $ BUNDLE="$TESTDIR/missing-comment.hg"
355 357 $ hg init missing
356 358 $ cd missing
357 359 $ hg unbundle $BUNDLE
358 360 adding changesets
359 361 adding manifests
360 362 adding file changes
361 363 added 3 changesets with 3 changes to 1 files
362 364 (run 'hg update' to get a working copy)
363 365 $ hg co tip
364 366 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
365 367 $ hg log --graph
366 368 @ changeset: 2:bd22688093b3
367 369 | tag: tip
368 370 | user: Robert Altman <robert.altman@telventDTN.com>
369 371 | date: Mon Nov 28 16:40:04 2011 +0000
370 372 | summary: Update file.
371 373 |
372 374 o changeset: 1:3b3e956f9171
373 375 | user: Robert Altman <robert.altman@telventDTN.com>
374 376 | date: Mon Nov 28 16:37:57 2011 +0000
375 377 |
376 378 o changeset: 0:141947992243
377 379 user: Robert Altman <robert.altman@telventDTN.com>
378 380 date: Mon Nov 28 16:35:28 2011 +0000
379 381 summary: Checked in text file
380 382
381 383 $ hg histedit 0
382 384 $ cd ..
383 385
384 386 $ cd ..
385 387
386 388
387 389 Test to make sure folding renames doesn't cause bogus conflicts (issue4251):
388 390 $ hg init issue4251
389 391 $ cd issue4251
390 392
391 393 $ mkdir initial-dir
392 394 $ echo foo > initial-dir/initial-file
393 395 $ hg add initial-dir/initial-file
394 396 $ hg commit -m "initial commit"
395 397
396 398 Move the file to a new directory, and in the same commit, change its content:
397 399 $ mkdir another-dir
398 400 $ hg mv initial-dir/initial-file another-dir/
399 401 $ echo changed > another-dir/initial-file
400 402 $ hg commit -m "moved and changed"
401 403
402 404 Rename the file:
403 405 $ hg mv another-dir/initial-file another-dir/renamed-file
404 406 $ hg commit -m "renamed"
405 407
406 408 Now, let's try to fold the second commit into the first:
407 409 $ cat > editor.sh <<EOF
408 410 > #!/bin/sh
409 411 > cat > \$1 <<ENDOF
410 412 > pick b0f4233702ca 0 initial commit
411 413 > fold 5e8704a8f2d2 1 moved and changed
412 414 > pick 40e7299e8fa7 2 renamed
413 415 > ENDOF
414 416 > EOF
415 417
416 418 $ HGEDITOR="sh ./editor.sh" hg histedit 0
417 419 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
418 420 adding another-dir/initial-file (glob)
419 421 removing initial-dir/initial-file (glob)
420 422 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
421 423 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
422 424 saved backup bundle to $TESTTMP/issue4251/.hg/strip-backup/*-backup.hg (glob)
423 425 saved backup bundle to $TESTTMP/issue4251/.hg/strip-backup/*-backup.hg (glob)
424 426
425 427 $ hg --config diff.git=yes export 0
426 428 # HG changeset patch
427 429 # User test
428 430 # Date 0 0
429 431 # Thu Jan 01 00:00:00 1970 +0000
430 432 # Node ID fffadc26f8f85623ce60b028a3f1ccc3730f8530
431 433 # Parent 0000000000000000000000000000000000000000
432 434 pick b0f4233702ca 0 initial commit
433 435 fold 5e8704a8f2d2 1 moved and changed
434 436 pick 40e7299e8fa7 2 renamed
435 437
436 438 diff --git a/another-dir/initial-file b/another-dir/initial-file
437 439 new file mode 100644
438 440 --- /dev/null
439 441 +++ b/another-dir/initial-file
440 442 @@ -0,0 +1,1 @@
441 443 +changed
442 444
443 445 $ hg --config diff.git=yes export 1
444 446 # HG changeset patch
445 447 # User test
446 448 # Date 0 0
447 449 # Thu Jan 01 00:00:00 1970 +0000
448 450 # Node ID 9b730d82b00af8a2766facebfa47cc124405a118
449 451 # Parent fffadc26f8f85623ce60b028a3f1ccc3730f8530
450 452 renamed
451 453
452 454 diff --git a/another-dir/initial-file b/another-dir/renamed-file
453 455 rename from another-dir/initial-file
454 456 rename to another-dir/renamed-file
455 457
456 458 $ cd ..
@@ -1,478 +1,479 b''
1 1 $ . "$TESTDIR/histedit-helpers.sh"
2 2
3 3 $ cat >> $HGRCPATH <<EOF
4 4 > [extensions]
5 5 > histedit=
6 6 > strip=
7 7 > EOF
8 8
9 9 $ initrepo ()
10 10 > {
11 11 > hg init r
12 12 > cd r
13 13 > for x in a b c d e f g; do
14 14 > echo $x > $x
15 15 > hg add $x
16 16 > hg ci -m $x
17 17 > done
18 18 > }
19 19
20 20 $ initrepo
21 21
22 22 log before edit
23 23 $ hg log --graph
24 24 @ changeset: 6:3c6a8ed2ebe8
25 25 | tag: tip
26 26 | user: test
27 27 | date: Thu Jan 01 00:00:00 1970 +0000
28 28 | summary: g
29 29 |
30 30 o changeset: 5:652413bf663e
31 31 | user: test
32 32 | date: Thu Jan 01 00:00:00 1970 +0000
33 33 | summary: f
34 34 |
35 35 o changeset: 4:e860deea161a
36 36 | user: test
37 37 | date: Thu Jan 01 00:00:00 1970 +0000
38 38 | summary: e
39 39 |
40 40 o changeset: 3:055a42cdd887
41 41 | user: test
42 42 | date: Thu Jan 01 00:00:00 1970 +0000
43 43 | summary: d
44 44 |
45 45 o changeset: 2:177f92b77385
46 46 | user: test
47 47 | date: Thu Jan 01 00:00:00 1970 +0000
48 48 | summary: c
49 49 |
50 50 o changeset: 1:d2ae7f538514
51 51 | user: test
52 52 | date: Thu Jan 01 00:00:00 1970 +0000
53 53 | summary: b
54 54 |
55 55 o changeset: 0:cb9a9f314b8b
56 56 user: test
57 57 date: Thu Jan 01 00:00:00 1970 +0000
58 58 summary: a
59 59
60 60 dirty a file
61 61 $ echo a > g
62 62 $ hg histedit 177f92b77385 --commands - 2>&1 << EOF
63 63 > EOF
64 64 abort: uncommitted changes
65 65 [255]
66 66 $ echo g > g
67 67
68 68 edit the history
69 69 $ hg histedit 177f92b77385 --commands - 2>&1 << EOF| fixbundle
70 70 > pick 177f92b77385 c
71 71 > pick 055a42cdd887 d
72 72 > edit e860deea161a e
73 73 > pick 652413bf663e f
74 74 > pick 3c6a8ed2ebe8 g
75 75 > EOF
76 76 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
77 77 Editing (e860deea161a), you may commit or record as needed now.
78 78 (hg histedit --continue to resume)
79 79
80 80 edit the plan via the editor
81 81 $ cat >> $TESTTMP/editplan.sh <<EOF
82 82 > cat > \$1 <<EOF2
83 83 > drop e860deea161a e
84 84 > drop 652413bf663e f
85 85 > drop 3c6a8ed2ebe8 g
86 86 > EOF2
87 87 > EOF
88 88 $ HGEDITOR="sh $TESTTMP/editplan.sh" hg histedit --edit-plan
89 89 $ cat .hg/histedit-state
90 90 v1
91 91 055a42cdd88768532f9cf79daa407fc8d138de9b
92 92 3c6a8ed2ebe862cc949d2caa30775dd6f16fb799
93 93 False
94 94 3
95 95 drop
96 96 e860deea161a2f77de56603b340ebbb4536308ae
97 97 drop
98 98 652413bf663ef2a641cab26574e46d5f5a64a55a
99 99 drop
100 100 3c6a8ed2ebe862cc949d2caa30775dd6f16fb799
101 101 0
102 102 strip-backup/177f92b77385-0ebe6a8f-histedit.hg
103 103
104 104 edit the plan via --commands
105 105 $ hg histedit --edit-plan --commands - 2>&1 << EOF
106 106 > edit e860deea161a e
107 107 > pick 652413bf663e f
108 108 > drop 3c6a8ed2ebe8 g
109 109 > EOF
110 110 $ cat .hg/histedit-state
111 111 v1
112 112 055a42cdd88768532f9cf79daa407fc8d138de9b
113 113 3c6a8ed2ebe862cc949d2caa30775dd6f16fb799
114 114 False
115 115 3
116 116 edit
117 117 e860deea161a2f77de56603b340ebbb4536308ae
118 118 pick
119 119 652413bf663ef2a641cab26574e46d5f5a64a55a
120 120 drop
121 121 3c6a8ed2ebe862cc949d2caa30775dd6f16fb799
122 122 0
123 123 strip-backup/177f92b77385-0ebe6a8f-histedit.hg
124 124
125 125 Go at a random point and try to continue
126 126
127 127 $ hg id -n
128 128 3+
129 129 $ hg up 0
130 130 abort: histedit in progress
131 131 (use 'hg histedit --continue' or 'hg histedit --abort')
132 132 [255]
133 133
134 134 Try to delete necessary commit
135 135 $ hg strip -r 652413b
136 136 abort: histedit in progress, can't strip 652413bf663e
137 137 [255]
138 138
139 139 commit, then edit the revision
140 140 $ hg ci -m 'wat'
141 141 created new head
142 142 $ echo a > e
143 143
144 144 qnew should fail while we're in the middle of the edit step
145 145
146 146 $ hg --config extensions.mq= qnew please-fail
147 147 abort: histedit in progress
148 148 (use 'hg histedit --continue' or 'hg histedit --abort')
149 149 [255]
150 150 $ HGEDITOR='echo foobaz > ' hg histedit --continue 2>&1 | fixbundle
151 151
152 152 $ hg log --graph
153 153 @ changeset: 6:b5f70786f9b0
154 154 | tag: tip
155 155 | user: test
156 156 | date: Thu Jan 01 00:00:00 1970 +0000
157 157 | summary: f
158 158 |
159 159 o changeset: 5:a5e1ba2f7afb
160 160 | user: test
161 161 | date: Thu Jan 01 00:00:00 1970 +0000
162 162 | summary: foobaz
163 163 |
164 164 o changeset: 4:1a60820cd1f6
165 165 | user: test
166 166 | date: Thu Jan 01 00:00:00 1970 +0000
167 167 | summary: wat
168 168 |
169 169 o changeset: 3:055a42cdd887
170 170 | user: test
171 171 | date: Thu Jan 01 00:00:00 1970 +0000
172 172 | summary: d
173 173 |
174 174 o changeset: 2:177f92b77385
175 175 | user: test
176 176 | date: Thu Jan 01 00:00:00 1970 +0000
177 177 | summary: c
178 178 |
179 179 o changeset: 1:d2ae7f538514
180 180 | user: test
181 181 | date: Thu Jan 01 00:00:00 1970 +0000
182 182 | summary: b
183 183 |
184 184 o changeset: 0:cb9a9f314b8b
185 185 user: test
186 186 date: Thu Jan 01 00:00:00 1970 +0000
187 187 summary: a
188 188
189 189
190 190 $ hg cat e
191 191 a
192 192
193 193 Stripping necessary commits should not break --abort
194 194
195 195 $ hg histedit 1a60820cd1f6 --commands - 2>&1 << EOF| fixbundle
196 196 > edit 1a60820cd1f6 wat
197 197 > pick a5e1ba2f7afb foobaz
198 198 > pick b5f70786f9b0 g
199 199 > EOF
200 200 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
201 201 Editing (1a60820cd1f6), you may commit or record as needed now.
202 202 (hg histedit --continue to resume)
203 203
204 204 $ mv .hg/histedit-state .hg/histedit-state.bak
205 205 $ hg strip -q -r b5f70786f9b0
206 206 $ mv .hg/histedit-state.bak .hg/histedit-state
207 207 $ hg histedit --abort
208 208 adding changesets
209 209 adding manifests
210 210 adding file changes
211 211 added 1 changesets with 1 changes to 3 files
212 212 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
213 213 $ hg log -r .
214 214 changeset: 6:b5f70786f9b0
215 215 tag: tip
216 216 user: test
217 217 date: Thu Jan 01 00:00:00 1970 +0000
218 218 summary: f
219 219
220 220
221 221 check histedit_source
222 222
223 223 $ hg log --debug --rev 5
224 224 changeset: 5:a5e1ba2f7afb899ef1581cea528fd885d2fca70d
225 225 phase: draft
226 226 parent: 4:1a60820cd1f6004a362aa622ebc47d59bc48eb34
227 227 parent: -1:0000000000000000000000000000000000000000
228 228 manifest: 5:5ad3be8791f39117565557781f5464363b918a45
229 229 user: test
230 230 date: Thu Jan 01 00:00:00 1970 +0000
231 231 files: e
232 232 extra: branch=default
233 233 extra: histedit_source=e860deea161a2f77de56603b340ebbb4536308ae
234 234 description:
235 235 foobaz
236 236
237 237
238 238
239 239 $ hg histedit tip --commands - 2>&1 <<EOF| fixbundle
240 240 > edit b5f70786f9b0 f
241 241 > EOF
242 242 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
243 243 Editing (b5f70786f9b0), you may commit or record as needed now.
244 244 (hg histedit --continue to resume)
245 245 $ hg status
246 246 A f
247 247
248 248 $ hg summary
249 249 parent: 5:a5e1ba2f7afb
250 250 foobaz
251 251 branch: default
252 252 commit: 1 added (new branch head)
253 253 update: 1 new changesets (update)
254 254 phases: 7 draft
255 255 hist: 1 remaining (histedit --continue)
256 256
257 257 (test also that editor is invoked if histedit is continued for
258 258 "edit" action)
259 259
260 260 $ HGEDITOR='cat' hg histedit --continue
261 261 f
262 262
263 263
264 264 HG: Enter commit message. Lines beginning with 'HG:' are removed.
265 265 HG: Leave message empty to abort commit.
266 266 HG: --
267 267 HG: user: test
268 268 HG: branch 'default'
269 269 HG: added f
270 270 saved backup bundle to $TESTTMP/r/.hg/strip-backup/b5f70786f9b0-c28d9c86-backup.hg (glob)
271 271
272 272 $ hg status
273 273
274 274 log after edit
275 275 $ hg log --limit 1
276 276 changeset: 6:a107ee126658
277 277 tag: tip
278 278 user: test
279 279 date: Thu Jan 01 00:00:00 1970 +0000
280 280 summary: f
281 281
282 282
283 283 say we'll change the message, but don't.
284 284 $ cat > ../edit.sh <<EOF
285 285 > cat "\$1" | sed s/pick/mess/ > tmp
286 286 > mv tmp "\$1"
287 287 > EOF
288 288 $ HGEDITOR="sh ../edit.sh" hg histedit tip 2>&1 | fixbundle
289 289 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
290 290 $ hg status
291 291 $ hg log --limit 1
292 292 changeset: 6:1fd3b2fe7754
293 293 tag: tip
294 294 user: test
295 295 date: Thu Jan 01 00:00:00 1970 +0000
296 296 summary: f
297 297
298 298
299 299 modify the message
300 300
301 301 check saving last-message.txt, at first
302 302
303 303 $ cat > $TESTTMP/commitfailure.py <<EOF
304 304 > from mercurial import error
305 305 > def reposetup(ui, repo):
306 306 > class commitfailure(repo.__class__):
307 307 > def commit(self, *args, **kwargs):
308 308 > raise error.Abort('emulating unexpected abort')
309 309 > repo.__class__ = commitfailure
310 310 > EOF
311 311 $ cat >> .hg/hgrc <<EOF
312 312 > [extensions]
313 313 > # this failure occurs before editor invocation
314 314 > commitfailure = $TESTTMP/commitfailure.py
315 315 > EOF
316 316
317 317 $ cat > $TESTTMP/editor.sh <<EOF
318 318 > echo "==== before editing"
319 319 > cat \$1
320 320 > echo "===="
321 321 > echo "check saving last-message.txt" >> \$1
322 322 > EOF
323 323
324 324 (test that editor is not invoked before transaction starting)
325 325
326 326 $ rm -f .hg/last-message.txt
327 327 $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit tip --commands - 2>&1 << EOF | fixbundle
328 328 > mess 1fd3b2fe7754 f
329 329 > EOF
330 330 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
331 331 abort: emulating unexpected abort
332 332 $ test -f .hg/last-message.txt
333 333 [1]
334 334
335 335 $ cat >> .hg/hgrc <<EOF
336 336 > [extensions]
337 337 > commitfailure = !
338 338 > EOF
339 339 $ hg histedit --abort -q
340 340
341 341 (test that editor is invoked and commit message is saved into
342 342 "last-message.txt")
343 343
344 344 $ cat >> .hg/hgrc <<EOF
345 345 > [hooks]
346 346 > # this failure occurs after editor invocation
347 347 > pretxncommit.unexpectedabort = false
348 348 > EOF
349 349
350 350 $ hg status --rev '1fd3b2fe7754^1' --rev 1fd3b2fe7754
351 351 A f
352 352
353 353 $ rm -f .hg/last-message.txt
354 354 $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit tip --commands - 2>&1 << EOF
355 355 > mess 1fd3b2fe7754 f
356 356 > EOF
357 357 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
358 358 adding f
359 359 ==== before editing
360 360 f
361 361
362 362
363 363 HG: Enter commit message. Lines beginning with 'HG:' are removed.
364 364 HG: Leave message empty to abort commit.
365 365 HG: --
366 366 HG: user: test
367 367 HG: branch 'default'
368 368 HG: added f
369 369 ====
370 370 note: commit message saved in .hg/last-message.txt
371 371 transaction abort!
372 372 rollback completed
373 373 abort: pretxncommit.unexpectedabort hook exited with status 1
374 374 [255]
375 375 $ cat .hg/last-message.txt
376 376 f
377 377
378 378
379 379 check saving last-message.txt
380 380
381 381 (test also that editor is invoked if histedit is continued for "message"
382 382 action)
383 383
384 384 $ HGEDITOR=cat hg histedit --continue
385 385 f
386 386
387 387
388 388 HG: Enter commit message. Lines beginning with 'HG:' are removed.
389 389 HG: Leave message empty to abort commit.
390 390 HG: --
391 391 HG: user: test
392 392 HG: branch 'default'
393 393 HG: added f
394 394 note: commit message saved in .hg/last-message.txt
395 395 transaction abort!
396 396 rollback completed
397 397 abort: pretxncommit.unexpectedabort hook exited with status 1
398 398 [255]
399 399
400 400 $ cat >> .hg/hgrc <<EOF
401 401 > [hooks]
402 402 > pretxncommit.unexpectedabort =
403 403 > EOF
404 404 $ hg histedit --abort -q
405 405
406 406 then, check "modify the message" itself
407 407
408 408 $ hg histedit tip --commands - 2>&1 << EOF | fixbundle
409 409 > mess 1fd3b2fe7754 f
410 410 > EOF
411 411 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
412 412 $ hg status
413 413 $ hg log --limit 1
414 414 changeset: 6:62feedb1200e
415 415 tag: tip
416 416 user: test
417 417 date: Thu Jan 01 00:00:00 1970 +0000
418 418 summary: f
419 419
420 420
421 421 rollback should not work after a histedit
422 422 $ hg rollback
423 423 no rollback information available
424 424 [1]
425 425
426 426 $ cd ..
427 427 $ hg clone -qr0 r r0
428 428 $ cd r0
429 429 $ hg phase -fdr0
430 430 $ hg histedit --commands - 0 2>&1 << EOF
431 431 > edit cb9a9f314b8b a > $EDITED
432 432 > EOF
433 433 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
434 434 adding a
435 435 Editing (cb9a9f314b8b), you may commit or record as needed now.
436 436 (hg histedit --continue to resume)
437 437 [1]
438 438 $ HGEDITOR=true hg histedit --continue
439 439 saved backup bundle to $TESTTMP/r0/.hg/strip-backup/cb9a9f314b8b-cc5ccb0b-backup.hg (glob)
440 440
441 441 $ hg log -G
442 442 @ changeset: 0:0efcea34f18a
443 443 tag: tip
444 444 user: test
445 445 date: Thu Jan 01 00:00:00 1970 +0000
446 446 summary: a
447 447
448 448 $ echo foo >> b
449 449 $ hg addr
450 450 adding b
451 451 $ hg ci -m 'add b'
452 452 $ echo foo >> a
453 453 $ hg ci -m 'extend a'
454 454 $ hg phase --public 1
455 455 Attempting to fold a change into a public change should not work:
456 456 $ cat > ../edit.sh <<EOF
457 457 > cat "\$1" | sed s/pick/fold/ > tmp
458 458 > mv tmp "\$1"
459 459 > EOF
460 460 $ HGEDITOR="sh ../edit.sh" hg histedit 2
461 461 warning: histedit rules saved to: .hg/histedit-last-edit.txt
462 462 hg: parse error: cannot fold into public change 18aa70c8ad22
463 463 [255]
464 464 $ cat .hg/histedit-last-edit.txt
465 465 fold 0012be4a27ea 2 extend a
466 466
467 467 # Edit history between 0012be4a27ea and 0012be4a27ea
468 468 #
469 469 # Commits are listed from least to most recent
470 470 #
471 471 # Commands:
472 #
473 # e, edit = use commit, but stop for amending
474 # m, mess = edit commit message without changing commit content
472 475 # p, fold = use commit
473 # e, edit = use commit, but stop for amending
476 # d, drop = remove commit from history
474 477 # f, fold = use commit, but combine it with the one above
475 478 # r, roll = like fold, but discard this commit's description
476 # d, drop = remove commit from history
477 # m, mess = edit commit message without changing commit content
478 479 #
@@ -1,442 +1,443 b''
1 1 $ . "$TESTDIR/histedit-helpers.sh"
2 2
3 3 Enable obsolete
4 4
5 5 $ cat >> $HGRCPATH << EOF
6 6 > [ui]
7 7 > logtemplate= {rev}:{node|short} {desc|firstline}
8 8 > [phases]
9 9 > publish=False
10 10 > [experimental]
11 11 > evolution=createmarkers,allowunstable
12 12 > [extensions]
13 13 > histedit=
14 14 > rebase=
15 15 > EOF
16 16
17 17 $ hg init base
18 18 $ cd base
19 19
20 20 $ for x in a b c d e f ; do
21 21 > echo $x > $x
22 22 > hg add $x
23 23 > hg ci -m $x
24 24 > done
25 25
26 26 $ hg log --graph
27 27 @ 5:652413bf663e f
28 28 |
29 29 o 4:e860deea161a e
30 30 |
31 31 o 3:055a42cdd887 d
32 32 |
33 33 o 2:177f92b77385 c
34 34 |
35 35 o 1:d2ae7f538514 b
36 36 |
37 37 o 0:cb9a9f314b8b a
38 38
39 39
40 40 $ HGEDITOR=cat hg histedit 1
41 41 pick d2ae7f538514 1 b
42 42 pick 177f92b77385 2 c
43 43 pick 055a42cdd887 3 d
44 44 pick e860deea161a 4 e
45 45 pick 652413bf663e 5 f
46 46
47 47 # Edit history between d2ae7f538514 and 652413bf663e
48 48 #
49 49 # Commits are listed from least to most recent
50 50 #
51 51 # Commands:
52 #
53 # e, edit = use commit, but stop for amending
54 # m, mess = edit commit message without changing commit content
52 55 # p, pick = use commit
53 # e, edit = use commit, but stop for amending
56 # d, drop = remove commit from history
54 57 # f, fold = use commit, but combine it with the one above
55 58 # r, roll = like fold, but discard this commit's description
56 # d, drop = remove commit from history
57 # m, mess = edit commit message without changing commit content
58 59 #
59 60 $ hg histedit 1 --commands - --verbose <<EOF | grep histedit
60 61 > pick 177f92b77385 2 c
61 62 > drop d2ae7f538514 1 b
62 63 > pick 055a42cdd887 3 d
63 64 > fold e860deea161a 4 e
64 65 > pick 652413bf663e 5 f
65 66 > EOF
66 67 [1]
67 68 $ hg log --graph --hidden
68 69 @ 10:cacdfd884a93 f
69 70 |
70 71 o 9:59d9f330561f d
71 72 |
72 73 | x 8:b558abc46d09 fold-temp-revision e860deea161a
73 74 | |
74 75 | x 7:96e494a2d553 d
75 76 |/
76 77 o 6:b346ab9a313d c
77 78 |
78 79 | x 5:652413bf663e f
79 80 | |
80 81 | x 4:e860deea161a e
81 82 | |
82 83 | x 3:055a42cdd887 d
83 84 | |
84 85 | x 2:177f92b77385 c
85 86 | |
86 87 | x 1:d2ae7f538514 b
87 88 |/
88 89 o 0:cb9a9f314b8b a
89 90
90 91 $ hg debugobsolete
91 92 96e494a2d553dd05902ba1cee1d94d4cb7b8faed 0 {b346ab9a313db8537ecf96fca3ca3ca984ef3bd7} (*) {'user': 'test'} (glob)
92 93 b558abc46d09c30f57ac31e85a8a3d64d2e906e4 0 {96e494a2d553dd05902ba1cee1d94d4cb7b8faed} (*) {'user': 'test'} (glob)
93 94 d2ae7f538514cd87c17547b0de4cea71fe1af9fb 0 {cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b} (*) {'user': 'test'} (glob)
94 95 177f92b773850b59254aa5e923436f921b55483b b346ab9a313db8537ecf96fca3ca3ca984ef3bd7 0 (*) {'user': 'test'} (glob)
95 96 055a42cdd88768532f9cf79daa407fc8d138de9b 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob)
96 97 e860deea161a2f77de56603b340ebbb4536308ae 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob)
97 98 652413bf663ef2a641cab26574e46d5f5a64a55a cacdfd884a9321ec4e1de275ef3949fa953a1f83 0 (*) {'user': 'test'} (glob)
98 99
99 100
100 101 Ensure hidden revision does not prevent histedit
101 102 -------------------------------------------------
102 103
103 104 create an hidden revision
104 105
105 106 $ hg histedit 6 --commands - << EOF
106 107 > pick b346ab9a313d 6 c
107 108 > drop 59d9f330561f 7 d
108 109 > pick cacdfd884a93 8 f
109 110 > EOF
110 111 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
111 112 $ hg log --graph
112 113 @ 11:c13eb81022ca f
113 114 |
114 115 o 6:b346ab9a313d c
115 116 |
116 117 o 0:cb9a9f314b8b a
117 118
118 119 check hidden revision are ignored (6 have hidden children 7 and 8)
119 120
120 121 $ hg histedit 6 --commands - << EOF
121 122 > pick b346ab9a313d 6 c
122 123 > pick c13eb81022ca 8 f
123 124 > EOF
124 125
125 126
126 127
127 128 Test that rewriting leaving instability behind is allowed
128 129 ---------------------------------------------------------------------
129 130
130 131 $ hg up '.^'
131 132 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
132 133 $ hg log -r 'children(.)'
133 134 11:c13eb81022ca f (no-eol)
134 135 $ hg histedit -r '.' --commands - <<EOF
135 136 > edit b346ab9a313d 6 c
136 137 > EOF
137 138 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
138 139 adding c
139 140 Editing (b346ab9a313d), you may commit or record as needed now.
140 141 (hg histedit --continue to resume)
141 142 [1]
142 143 $ echo c >> c
143 144 $ hg histedit --continue
144 145
145 146 $ hg log -r 'unstable()'
146 147 11:c13eb81022ca f (no-eol)
147 148
148 149 stabilise
149 150
150 151 $ hg rebase -r 'unstable()' -d .
151 152 rebasing 11:c13eb81022ca "f"
152 153 $ hg up tip -q
153 154
154 155 check that extra has accumulated from histedit and rebase
155 156
156 157 $ hg log -T '{extras % "{key}={value}\n"}\n' -r tip
157 158 branch=default
158 159 histedit_source=cacdfd884a9321ec4e1de275ef3949fa953a1f83
159 160 rebase_source=c13eb81022caa686a369223fe7f926bc4f7db576
160 161
161 162
162 163 Test dropping of changeset on the top of the stack
163 164 -------------------------------------------------------
164 165
165 166 Nothing is rewritten below, the working directory parent must be change for the
166 167 dropped changeset to be hidden.
167 168
168 169 $ cd ..
169 170 $ hg clone base droplast
170 171 updating to branch default
171 172 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
172 173 $ cd droplast
173 174 $ hg histedit -r '40db8afa467b' --commands - << EOF
174 175 > pick 40db8afa467b 10 c
175 176 > drop 947ece25170f 11 f
176 177 > EOF
177 178 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
178 179 $ hg log -G
179 180 @ 12:40db8afa467b c
180 181 |
181 182 o 0:cb9a9f314b8b a
182 183
183 184
184 185 With rewritten ancestors
185 186
186 187 $ echo e > e
187 188 $ hg add e
188 189 $ hg commit -m g
189 190 $ echo f > f
190 191 $ hg add f
191 192 $ hg commit -m h
192 193 $ hg histedit -r '40db8afa467b' --commands - << EOF
193 194 > pick 47a8561c0449 12 g
194 195 > pick 40db8afa467b 10 c
195 196 > drop 1b3b05f35ff0 13 h
196 197 > EOF
197 198 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
198 199 $ hg log -G
199 200 @ 17:ee6544123ab8 c
200 201 |
201 202 o 16:269e713e9eae g
202 203 |
203 204 o 0:cb9a9f314b8b a
204 205
205 206 $ cd ../base
206 207
207 208
208 209
209 210 Test phases support
210 211 ===========================================
211 212
212 213 Check that histedit respect immutability
213 214 -------------------------------------------
214 215
215 216 $ cat >> $HGRCPATH << EOF
216 217 > [ui]
217 218 > logtemplate= {rev}:{node|short} ({phase}) {desc|firstline}\n
218 219 > EOF
219 220
220 221 $ hg ph -pv '.^'
221 222 phase changed for 2 changesets
222 223 $ hg log -G
223 224 @ 13:947ece25170f (draft) f
224 225 |
225 226 o 12:40db8afa467b (public) c
226 227 |
227 228 o 0:cb9a9f314b8b (public) a
228 229
229 230 $ hg histedit -r '.~2'
230 231 abort: cannot edit public changeset: cb9a9f314b8b
231 232 (see "hg help phases" for details)
232 233 [255]
233 234
234 235
235 236 Prepare further testing
236 237 -------------------------------------------
237 238
238 239 $ for x in g h i j k ; do
239 240 > echo $x > $x
240 241 > hg add $x
241 242 > hg ci -m $x
242 243 > done
243 244 $ hg phase --force --secret .~2
244 245 $ hg log -G
245 246 @ 18:14bda137d5b3 (secret) k
246 247 |
247 248 o 17:c62e7241a4f2 (secret) j
248 249 |
249 250 o 16:9cd3934e05af (secret) i
250 251 |
251 252 o 15:ee4a24fc4dfa (draft) h
252 253 |
253 254 o 14:d22905de3528 (draft) g
254 255 |
255 256 o 13:947ece25170f (draft) f
256 257 |
257 258 o 12:40db8afa467b (public) c
258 259 |
259 260 o 0:cb9a9f314b8b (public) a
260 261
261 262 $ cd ..
262 263
263 264 simple phase conservation
264 265 -------------------------------------------
265 266
266 267 Resulting changeset should conserve the phase of the original one whatever the
267 268 phases.new-commit option is.
268 269
269 270 New-commit as draft (default)
270 271
271 272 $ cp -r base simple-draft
272 273 $ cd simple-draft
273 274 $ hg histedit -r '947ece25170f' --commands - << EOF
274 275 > edit 947ece25170f 11 f
275 276 > pick d22905de3528 12 g
276 277 > pick ee4a24fc4dfa 13 h
277 278 > pick 9cd3934e05af 14 i
278 279 > pick c62e7241a4f2 15 j
279 280 > pick 14bda137d5b3 16 k
280 281 > EOF
281 282 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
282 283 adding f
283 284 Editing (947ece25170f), you may commit or record as needed now.
284 285 (hg histedit --continue to resume)
285 286 [1]
286 287 $ echo f >> f
287 288 $ hg histedit --continue
288 289 $ hg log -G
289 290 @ 24:12925f763c90 (secret) k
290 291 |
291 292 o 23:4545a6e77442 (secret) j
292 293 |
293 294 o 22:d947a0798e76 (secret) i
294 295 |
295 296 o 21:28fb35ae4ebb (draft) h
296 297 |
297 298 o 20:10b22a5a9645 (draft) g
298 299 |
299 300 o 19:c5a1db4a69f5 (draft) f
300 301 |
301 302 o 12:40db8afa467b (public) c
302 303 |
303 304 o 0:cb9a9f314b8b (public) a
304 305
305 306 $ cd ..
306 307
307 308
308 309 New-commit as draft (default)
309 310
310 311 $ cp -r base simple-secret
311 312 $ cd simple-secret
312 313 $ cat >> .hg/hgrc << EOF
313 314 > [phases]
314 315 > new-commit=secret
315 316 > EOF
316 317 $ hg histedit -r '947ece25170f' --commands - << EOF
317 318 > edit 947ece25170f 11 f
318 319 > pick d22905de3528 12 g
319 320 > pick ee4a24fc4dfa 13 h
320 321 > pick 9cd3934e05af 14 i
321 322 > pick c62e7241a4f2 15 j
322 323 > pick 14bda137d5b3 16 k
323 324 > EOF
324 325 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
325 326 adding f
326 327 Editing (947ece25170f), you may commit or record as needed now.
327 328 (hg histedit --continue to resume)
328 329 [1]
329 330 $ echo f >> f
330 331 $ hg histedit --continue
331 332 $ hg log -G
332 333 @ 24:12925f763c90 (secret) k
333 334 |
334 335 o 23:4545a6e77442 (secret) j
335 336 |
336 337 o 22:d947a0798e76 (secret) i
337 338 |
338 339 o 21:28fb35ae4ebb (draft) h
339 340 |
340 341 o 20:10b22a5a9645 (draft) g
341 342 |
342 343 o 19:c5a1db4a69f5 (draft) f
343 344 |
344 345 o 12:40db8afa467b (public) c
345 346 |
346 347 o 0:cb9a9f314b8b (public) a
347 348
348 349 $ cd ..
349 350
350 351
351 352 Changeset reordering
352 353 -------------------------------------------
353 354
354 355 If a secret changeset is put before a draft one, all descendant should be secret.
355 356 It seems more important to present the secret phase.
356 357
357 358 $ cp -r base reorder
358 359 $ cd reorder
359 360 $ hg histedit -r '947ece25170f' --commands - << EOF
360 361 > pick 947ece25170f 11 f
361 362 > pick c62e7241a4f2 15 j
362 363 > pick d22905de3528 12 g
363 364 > pick 9cd3934e05af 14 i
364 365 > pick ee4a24fc4dfa 13 h
365 366 > pick 14bda137d5b3 16 k
366 367 > EOF
367 368 0 files updated, 0 files merged, 5 files removed, 0 files unresolved
368 369 $ hg log -G
369 370 @ 23:9e712162b2c1 (secret) k
370 371 |
371 372 o 22:490861543602 (secret) h
372 373 |
373 374 o 21:86aeda50b70d (secret) i
374 375 |
375 376 o 20:b2fa360bc090 (secret) g
376 377 |
377 378 o 19:e10fb4e3eb8e (secret) j
378 379 |
379 380 o 13:947ece25170f (draft) f
380 381 |
381 382 o 12:40db8afa467b (public) c
382 383 |
383 384 o 0:cb9a9f314b8b (public) a
384 385
385 386 $ cd ..
386 387
387 388 Changeset folding
388 389 -------------------------------------------
389 390
390 391 Folding a secret changeset with a draft one turn the result secret (again,
391 392 better safe than sorry). Folding between same phase changeset still works
392 393
393 394 Note that there is a few reordering in this series for more extensive test
394 395
395 396 $ cp -r base folding
396 397 $ cd folding
397 398 $ cat >> .hg/hgrc << EOF
398 399 > [phases]
399 400 > new-commit=secret
400 401 > EOF
401 402 $ hg histedit -r '947ece25170f' --commands - << EOF
402 403 > pick ee4a24fc4dfa 13 h
403 404 > fold 947ece25170f 11 f
404 405 > pick d22905de3528 12 g
405 406 > fold c62e7241a4f2 15 j
406 407 > pick 9cd3934e05af 14 i
407 408 > fold 14bda137d5b3 16 k
408 409 > EOF
409 410 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
410 411 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
411 412 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
412 413 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
413 414 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
414 415 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
415 416 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
416 417 $ hg log -G
417 418 @ 27:769e8ee8708e (secret) i
418 419 |
419 420 o 24:3de6dbab1b62 (secret) g
420 421 |
421 422 o 21:1d51647632b2 (draft) h
422 423 |
423 424 o 12:40db8afa467b (public) c
424 425 |
425 426 o 0:cb9a9f314b8b (public) a
426 427
427 428 $ hg co 3de6dbab1b62
428 429 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
429 430 $ echo wat >> wat
430 431 $ hg add wat
431 432 $ hg ci -m 'add wat'
432 433 created new head
433 434 $ hg merge 769e8ee8708e
434 435 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
435 436 (branch merge, don't forget to commit)
436 437 $ hg ci -m 'merge'
437 438 $ echo not wat > wat
438 439 $ hg ci -m 'modify wat'
439 440 $ hg histedit 1d51647632b2
440 441 abort: cannot edit history that contains merges
441 442 [255]
442 443 $ cd ..
@@ -1,144 +1,147 b''
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > histedit=
4 4 > EOF
5 5
6 6 $ initrepos ()
7 7 > {
8 8 > hg init r
9 9 > cd r
10 10 > for x in a b c ; do
11 11 > echo $x > $x
12 12 > hg add $x
13 13 > hg ci -m $x
14 14 > done
15 15 > cd ..
16 16 > hg clone r r2 | grep -v updating
17 17 > cd r2
18 18 > for x in d e f ; do
19 19 > echo $x > $x
20 20 > hg add $x
21 21 > hg ci -m $x
22 22 > done
23 23 > cd ..
24 24 > hg init r3
25 25 > cd r3
26 26 > for x in g h i ; do
27 27 > echo $x > $x
28 28 > hg add $x
29 29 > hg ci -m $x
30 30 > done
31 31 > cd ..
32 32 > }
33 33
34 34 $ initrepos
35 35 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 36
37 37 show the edit commands offered by outgoing
38 38 $ cd r2
39 39 $ HGEDITOR=cat hg histedit --outgoing ../r | grep -v comparing | grep -v searching
40 40 pick 055a42cdd887 3 d
41 41 pick e860deea161a 4 e
42 42 pick 652413bf663e 5 f
43 43
44 44 # Edit history between 055a42cdd887 and 652413bf663e
45 45 #
46 46 # Commits are listed from least to most recent
47 47 #
48 48 # Commands:
49 #
50 # e, edit = use commit, but stop for amending
51 # m, mess = edit commit message without changing commit content
49 52 # p, pick = use commit
50 # e, edit = use commit, but stop for amending
53 # d, drop = remove commit from history
51 54 # f, fold = use commit, but combine it with the one above
52 55 # r, roll = like fold, but discard this commit's description
53 # d, drop = remove commit from history
54 # m, mess = edit commit message without changing commit content
55 56 #
56 57 $ cd ..
57 58
58 59 show the error from unrelated repos
59 60 $ cd r3
60 61 $ HGEDITOR=cat hg histedit --outgoing ../r | grep -v comparing | grep -v searching
61 62 abort: repository is unrelated
62 63 [1]
63 64 $ cd ..
64 65
65 66 show the error from unrelated repos
66 67 $ cd r3
67 68 $ HGEDITOR=cat hg histedit --force --outgoing ../r
68 69 comparing with ../r
69 70 searching for changes
70 71 warning: repository is unrelated
71 72 pick 2a4042b45417 0 g
72 73 pick 68c46b4927ce 1 h
73 74 pick 51281e65ba79 2 i
74 75
75 76 # Edit history between 2a4042b45417 and 51281e65ba79
76 77 #
77 78 # Commits are listed from least to most recent
78 79 #
79 80 # Commands:
81 #
82 # e, edit = use commit, but stop for amending
83 # m, mess = edit commit message without changing commit content
80 84 # p, pick = use commit
81 # e, edit = use commit, but stop for amending
85 # d, drop = remove commit from history
82 86 # f, fold = use commit, but combine it with the one above
83 87 # r, roll = like fold, but discard this commit's description
84 # d, drop = remove commit from history
85 # m, mess = edit commit message without changing commit content
86 88 #
87 89 $ cd ..
88 90
89 91 test sensitivity to branch in URL:
90 92
91 93 $ cd r2
92 94 $ hg -q update 2
93 95 $ hg -q branch foo
94 96 $ hg commit -m 'create foo branch'
95 97 $ HGEDITOR=cat hg histedit --outgoing '../r#foo' | grep -v comparing | grep -v searching
96 98 pick f26599ee3441 6 create foo branch
97 99
98 100 # Edit history between f26599ee3441 and f26599ee3441
99 101 #
100 102 # Commits are listed from least to most recent
101 103 #
102 104 # Commands:
105 #
106 # e, edit = use commit, but stop for amending
107 # m, mess = edit commit message without changing commit content
103 108 # p, pick = use commit
104 # e, edit = use commit, but stop for amending
109 # d, drop = remove commit from history
105 110 # f, fold = use commit, but combine it with the one above
106 111 # r, roll = like fold, but discard this commit's description
107 # d, drop = remove commit from history
108 # m, mess = edit commit message without changing commit content
109 112 #
110 113
111 114 test to check number of roots in outgoing revisions
112 115
113 116 $ hg -q outgoing -G --template '{node|short}({branch})' '../r'
114 117 @ f26599ee3441(foo)
115 118
116 119 o 652413bf663e(default)
117 120 |
118 121 o e860deea161a(default)
119 122 |
120 123 o 055a42cdd887(default)
121 124
122 125 $ HGEDITOR=cat hg -q histedit --outgoing '../r'
123 126 abort: there are ambiguous outgoing revisions
124 127 (see "hg help histedit" for more detail)
125 128 [255]
126 129
127 130 $ hg -q update -C 2
128 131 $ echo aa >> a
129 132 $ hg -q commit -m 'another head on default'
130 133 $ hg -q outgoing -G --template '{node|short}({branch})' '../r#default'
131 134 @ 3879dc049647(default)
132 135
133 136 o 652413bf663e(default)
134 137 |
135 138 o e860deea161a(default)
136 139 |
137 140 o 055a42cdd887(default)
138 141
139 142 $ HGEDITOR=cat hg -q histedit --outgoing '../r#default'
140 143 abort: there are ambiguous outgoing revisions
141 144 (see "hg help histedit" for more detail)
142 145 [255]
143 146
144 147 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now