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