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