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