##// END OF EJS Templates
histedit: respect revsetalias entries (issue4311)...
Augie Fackler -
r21950:af44c7a1 stable
parent child Browse files
Show More
@@ -1,928 +1,929 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 # d, drop = remove commit from history
40 40 # m, mess = edit message without changing commit content
41 41 #
42 42
43 43 In this file, lines beginning with ``#`` are ignored. You must specify a rule
44 44 for each revision in your history. For example, if you had meant to add gamma
45 45 before beta, and then wanted to add delta in the same revision as beta, you
46 46 would reorganize the file to look like this::
47 47
48 48 pick 030b686bedc4 Add gamma
49 49 pick c561b4e977df Add beta
50 50 fold 7c2fd3b9020c Add delta
51 51
52 52 # Edit history between c561b4e977df and 7c2fd3b9020c
53 53 #
54 54 # Commits are listed from least to most recent
55 55 #
56 56 # Commands:
57 57 # p, pick = use commit
58 58 # e, edit = use commit, but stop for amending
59 59 # f, fold = use commit, but combine it with the one above
60 60 # d, drop = remove commit from history
61 61 # m, mess = edit message without changing commit content
62 62 #
63 63
64 64 At which point you close the editor and ``histedit`` starts working. When you
65 65 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
66 66 those revisions together, offering you a chance to clean up the commit message::
67 67
68 68 Add beta
69 69 ***
70 70 Add delta
71 71
72 72 Edit the commit message to your liking, then close the editor. For
73 73 this example, let's assume that the commit message was changed to
74 74 ``Add beta and delta.`` After histedit has run and had a chance to
75 75 remove any old or temporary revisions it needed, the history looks
76 76 like this::
77 77
78 78 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
79 79 | Add beta and delta.
80 80 |
81 81 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
82 82 | Add gamma
83 83 |
84 84 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
85 85 Add alpha
86 86
87 87 Note that ``histedit`` does *not* remove any revisions (even its own temporary
88 88 ones) until after it has completed all the editing operations, so it will
89 89 probably perform several strip operations when it's done. For the above example,
90 90 it had to run strip twice. Strip can be slow depending on a variety of factors,
91 91 so you might need to be a little patient. You can choose to keep the original
92 92 revisions by passing the ``--keep`` flag.
93 93
94 94 The ``edit`` operation will drop you back to a command prompt,
95 95 allowing you to edit files freely, or even use ``hg record`` to commit
96 96 some changes as a separate commit. When you're done, any remaining
97 97 uncommitted changes will be committed as well. When done, run ``hg
98 98 histedit --continue`` to finish this step. You'll be prompted for a
99 99 new commit message, but the default commit message will be the
100 100 original message for the ``edit`` ed revision.
101 101
102 102 The ``message`` operation will give you a chance to revise a commit
103 103 message without changing the contents. It's a shortcut for doing
104 104 ``edit`` immediately followed by `hg histedit --continue``.
105 105
106 106 If ``histedit`` encounters a conflict when moving a revision (while
107 107 handling ``pick`` or ``fold``), it'll stop in a similar manner to
108 108 ``edit`` with the difference that it won't prompt you for a commit
109 109 message when done. If you decide at this point that you don't like how
110 110 much work it will be to rearrange history, or that you made a mistake,
111 111 you can use ``hg histedit --abort`` to abandon the new changes you
112 112 have made and return to the state before you attempted to edit your
113 113 history.
114 114
115 115 If we clone the histedit-ed example repository above and add four more
116 116 changes, such that we have the following history::
117 117
118 118 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
119 119 | Add theta
120 120 |
121 121 o 5 140988835471 2009-04-27 18:04 -0500 stefan
122 122 | Add eta
123 123 |
124 124 o 4 122930637314 2009-04-27 18:04 -0500 stefan
125 125 | Add zeta
126 126 |
127 127 o 3 836302820282 2009-04-27 18:04 -0500 stefan
128 128 | Add epsilon
129 129 |
130 130 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
131 131 | Add beta and delta.
132 132 |
133 133 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
134 134 | Add gamma
135 135 |
136 136 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
137 137 Add alpha
138 138
139 139 If you run ``hg histedit --outgoing`` on the clone then it is the same
140 140 as running ``hg histedit 836302820282``. If you need plan to push to a
141 141 repository that Mercurial does not detect to be related to the source
142 142 repo, you can add a ``--force`` option.
143 143 """
144 144
145 145 try:
146 146 import cPickle as pickle
147 147 pickle.dump # import now
148 148 except ImportError:
149 149 import pickle
150 150 import os
151 151 import sys
152 152
153 153 from mercurial import cmdutil
154 154 from mercurial import discovery
155 155 from mercurial import error
156 156 from mercurial import copies
157 157 from mercurial import context
158 158 from mercurial import hg
159 159 from mercurial import node
160 160 from mercurial import repair
161 from mercurial import scmutil
161 162 from mercurial import util
162 163 from mercurial import obsolete
163 164 from mercurial import merge as mergemod
164 165 from mercurial.lock import release
165 166 from mercurial.i18n import _
166 167
167 168 cmdtable = {}
168 169 command = cmdutil.command(cmdtable)
169 170
170 171 testedwith = 'internal'
171 172
172 173 # i18n: command names and abbreviations must remain untranslated
173 174 editcomment = _("""# Edit history between %s and %s
174 175 #
175 176 # Commits are listed from least to most recent
176 177 #
177 178 # Commands:
178 179 # p, pick = use commit
179 180 # e, edit = use commit, but stop for amending
180 181 # f, fold = use commit, but combine it with the one above
181 182 # d, drop = remove commit from history
182 183 # m, mess = edit message without changing commit content
183 184 #
184 185 """)
185 186
186 187 def commitfuncfor(repo, src):
187 188 """Build a commit function for the replacement of <src>
188 189
189 190 This function ensure we apply the same treatment to all changesets.
190 191
191 192 - Add a 'histedit_source' entry in extra.
192 193
193 194 Note that fold have its own separated logic because its handling is a bit
194 195 different and not easily factored out of the fold method.
195 196 """
196 197 phasemin = src.phase()
197 198 def commitfunc(**kwargs):
198 199 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
199 200 try:
200 201 repo.ui.setconfig('phases', 'new-commit', phasemin,
201 202 'histedit')
202 203 extra = kwargs.get('extra', {}).copy()
203 204 extra['histedit_source'] = src.hex()
204 205 kwargs['extra'] = extra
205 206 return repo.commit(**kwargs)
206 207 finally:
207 208 repo.ui.restoreconfig(phasebackup)
208 209 return commitfunc
209 210
210 211
211 212
212 213 def applychanges(ui, repo, ctx, opts):
213 214 """Merge changeset from ctx (only) in the current working directory"""
214 215 wcpar = repo.dirstate.parents()[0]
215 216 if ctx.p1().node() == wcpar:
216 217 # edition ar "in place" we do not need to make any merge,
217 218 # just applies changes on parent for edition
218 219 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
219 220 stats = None
220 221 else:
221 222 try:
222 223 # ui.forcemerge is an internal variable, do not document
223 224 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
224 225 'histedit')
225 226 stats = mergemod.update(repo, ctx.node(), True, True, False,
226 227 ctx.p1().node())
227 228 finally:
228 229 repo.ui.setconfig('ui', 'forcemerge', '', 'histedit')
229 230 repo.setparents(wcpar, node.nullid)
230 231 repo.dirstate.write()
231 232 # fix up dirstate for copies and renames
232 233 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
233 234 return stats
234 235
235 236 def collapse(repo, first, last, commitopts):
236 237 """collapse the set of revisions from first to last as new one.
237 238
238 239 Expected commit options are:
239 240 - message
240 241 - date
241 242 - username
242 243 Commit message is edited in all cases.
243 244
244 245 This function works in memory."""
245 246 ctxs = list(repo.set('%d::%d', first, last))
246 247 if not ctxs:
247 248 return None
248 249 base = first.parents()[0]
249 250
250 251 # commit a new version of the old changeset, including the update
251 252 # collect all files which might be affected
252 253 files = set()
253 254 for ctx in ctxs:
254 255 files.update(ctx.files())
255 256
256 257 # Recompute copies (avoid recording a -> b -> a)
257 258 copied = copies.pathcopies(base, last)
258 259
259 260 # prune files which were reverted by the updates
260 261 def samefile(f):
261 262 if f in last.manifest():
262 263 a = last.filectx(f)
263 264 if f in base.manifest():
264 265 b = base.filectx(f)
265 266 return (a.data() == b.data()
266 267 and a.flags() == b.flags())
267 268 else:
268 269 return False
269 270 else:
270 271 return f not in base.manifest()
271 272 files = [f for f in files if not samefile(f)]
272 273 # commit version of these files as defined by head
273 274 headmf = last.manifest()
274 275 def filectxfn(repo, ctx, path):
275 276 if path in headmf:
276 277 fctx = last[path]
277 278 flags = fctx.flags()
278 279 mctx = context.memfilectx(repo,
279 280 fctx.path(), fctx.data(),
280 281 islink='l' in flags,
281 282 isexec='x' in flags,
282 283 copied=copied.get(path))
283 284 return mctx
284 285 raise IOError()
285 286
286 287 if commitopts.get('message'):
287 288 message = commitopts['message']
288 289 else:
289 290 message = first.description()
290 291 user = commitopts.get('user')
291 292 date = commitopts.get('date')
292 293 extra = commitopts.get('extra')
293 294
294 295 parents = (first.p1().node(), first.p2().node())
295 296 new = context.memctx(repo,
296 297 parents=parents,
297 298 text=message,
298 299 files=files,
299 300 filectxfn=filectxfn,
300 301 user=user,
301 302 date=date,
302 303 extra=extra,
303 304 editor=cmdutil.getcommiteditor(edit=True))
304 305 return repo.commitctx(new)
305 306
306 307 def pick(ui, repo, ctx, ha, opts):
307 308 oldctx = repo[ha]
308 309 if oldctx.parents()[0] == ctx:
309 310 ui.debug('node %s unchanged\n' % ha)
310 311 return oldctx, []
311 312 hg.update(repo, ctx.node())
312 313 stats = applychanges(ui, repo, oldctx, opts)
313 314 if stats and stats[3] > 0:
314 315 raise error.InterventionRequired(_('Fix up the change and run '
315 316 'hg histedit --continue'))
316 317 # drop the second merge parent
317 318 commit = commitfuncfor(repo, oldctx)
318 319 n = commit(text=oldctx.description(), user=oldctx.user(),
319 320 date=oldctx.date(), extra=oldctx.extra())
320 321 if n is None:
321 322 ui.warn(_('%s: empty changeset\n')
322 323 % node.hex(ha))
323 324 return ctx, []
324 325 new = repo[n]
325 326 return new, [(oldctx.node(), (n,))]
326 327
327 328
328 329 def edit(ui, repo, ctx, ha, opts):
329 330 oldctx = repo[ha]
330 331 hg.update(repo, ctx.node())
331 332 applychanges(ui, repo, oldctx, opts)
332 333 raise error.InterventionRequired(
333 334 _('Make changes as needed, you may commit or record as needed now.\n'
334 335 'When you are finished, run hg histedit --continue to resume.'))
335 336
336 337 def fold(ui, repo, ctx, ha, opts):
337 338 oldctx = repo[ha]
338 339 hg.update(repo, ctx.node())
339 340 stats = applychanges(ui, repo, oldctx, opts)
340 341 if stats and stats[3] > 0:
341 342 raise error.InterventionRequired(
342 343 _('Fix up the change and run hg histedit --continue'))
343 344 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
344 345 date=oldctx.date(), extra=oldctx.extra())
345 346 if n is None:
346 347 ui.warn(_('%s: empty changeset')
347 348 % node.hex(ha))
348 349 return ctx, []
349 350 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
350 351
351 352 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
352 353 parent = ctx.parents()[0].node()
353 354 hg.update(repo, parent)
354 355 ### prepare new commit data
355 356 commitopts = opts.copy()
356 357 # username
357 358 if ctx.user() == oldctx.user():
358 359 username = ctx.user()
359 360 else:
360 361 username = ui.username()
361 362 commitopts['user'] = username
362 363 # commit message
363 364 newmessage = '\n***\n'.join(
364 365 [ctx.description()] +
365 366 [repo[r].description() for r in internalchanges] +
366 367 [oldctx.description()]) + '\n'
367 368 commitopts['message'] = newmessage
368 369 # date
369 370 commitopts['date'] = max(ctx.date(), oldctx.date())
370 371 extra = ctx.extra().copy()
371 372 # histedit_source
372 373 # note: ctx is likely a temporary commit but that the best we can do here
373 374 # This is sufficient to solve issue3681 anyway
374 375 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex())
375 376 commitopts['extra'] = extra
376 377 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
377 378 try:
378 379 phasemin = max(ctx.phase(), oldctx.phase())
379 380 repo.ui.setconfig('phases', 'new-commit', phasemin, 'histedit')
380 381 n = collapse(repo, ctx, repo[newnode], commitopts)
381 382 finally:
382 383 repo.ui.restoreconfig(phasebackup)
383 384 if n is None:
384 385 return ctx, []
385 386 hg.update(repo, n)
386 387 replacements = [(oldctx.node(), (newnode,)),
387 388 (ctx.node(), (n,)),
388 389 (newnode, (n,)),
389 390 ]
390 391 for ich in internalchanges:
391 392 replacements.append((ich, (n,)))
392 393 return repo[n], replacements
393 394
394 395 def drop(ui, repo, ctx, ha, opts):
395 396 return ctx, [(repo[ha].node(), ())]
396 397
397 398
398 399 def message(ui, repo, ctx, ha, opts):
399 400 oldctx = repo[ha]
400 401 hg.update(repo, ctx.node())
401 402 stats = applychanges(ui, repo, oldctx, opts)
402 403 if stats and stats[3] > 0:
403 404 raise error.InterventionRequired(
404 405 _('Fix up the change and run hg histedit --continue'))
405 406 message = oldctx.description()
406 407 commit = commitfuncfor(repo, oldctx)
407 408 new = commit(text=message, user=oldctx.user(), date=oldctx.date(),
408 409 extra=oldctx.extra(),
409 410 editor=cmdutil.getcommiteditor(edit=True))
410 411 newctx = repo[new]
411 412 if oldctx.node() != newctx.node():
412 413 return newctx, [(oldctx.node(), (new,))]
413 414 # We didn't make an edit, so just indicate no replaced nodes
414 415 return newctx, []
415 416
416 417 def findoutgoing(ui, repo, remote=None, force=False, opts={}):
417 418 """utility function to find the first outgoing changeset
418 419
419 420 Used by initialisation code"""
420 421 dest = ui.expandpath(remote or 'default-push', remote or 'default')
421 422 dest, revs = hg.parseurl(dest, None)[:2]
422 423 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
423 424
424 425 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
425 426 other = hg.peer(repo, opts, dest)
426 427
427 428 if revs:
428 429 revs = [repo.lookup(rev) for rev in revs]
429 430
430 431 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
431 432 if not outgoing.missing:
432 433 raise util.Abort(_('no outgoing ancestors'))
433 434 roots = list(repo.revs("roots(%ln)", outgoing.missing))
434 435 if 1 < len(roots):
435 436 msg = _('there are ambiguous outgoing revisions')
436 437 hint = _('see "hg help histedit" for more detail')
437 438 raise util.Abort(msg, hint=hint)
438 439 return repo.lookup(roots[0])
439 440
440 441 actiontable = {'p': pick,
441 442 'pick': pick,
442 443 'e': edit,
443 444 'edit': edit,
444 445 'f': fold,
445 446 'fold': fold,
446 447 'd': drop,
447 448 'drop': drop,
448 449 'm': message,
449 450 'mess': message,
450 451 }
451 452
452 453 @command('histedit',
453 454 [('', 'commands', '',
454 455 _('Read history edits from the specified file.')),
455 456 ('c', 'continue', False, _('continue an edit already in progress')),
456 457 ('k', 'keep', False,
457 458 _("don't strip old nodes after edit is complete")),
458 459 ('', 'abort', False, _('abort an edit in progress')),
459 460 ('o', 'outgoing', False, _('changesets not found in destination')),
460 461 ('f', 'force', False,
461 462 _('force outgoing even for unrelated repositories')),
462 463 ('r', 'rev', [], _('first revision to be edited'))],
463 464 _("ANCESTOR | --outgoing [URL]"))
464 465 def histedit(ui, repo, *freeargs, **opts):
465 466 """interactively edit changeset history
466 467
467 468 This command edits changesets between ANCESTOR and the parent of
468 469 the working directory.
469 470
470 471 With --outgoing, this edits changesets not found in the
471 472 destination repository. If URL of the destination is omitted, the
472 473 'default-push' (or 'default') path will be used.
473 474
474 475 For safety, this command is aborted, also if there are ambiguous
475 476 outgoing revisions which may confuse users: for example, there are
476 477 multiple branches containing outgoing revisions.
477 478
478 479 Use "min(outgoing() and ::.)" or similar revset specification
479 480 instead of --outgoing to specify edit target revision exactly in
480 481 such ambiguous situation. See :hg:`help revsets` for detail about
481 482 selecting revisions.
482 483
483 484 Returns 0 on success, 1 if user intervention is required (not only
484 485 for intentional "edit" command, but also for resolving unexpected
485 486 conflicts).
486 487 """
487 488 lock = wlock = None
488 489 try:
489 490 wlock = repo.wlock()
490 491 lock = repo.lock()
491 492 _histedit(ui, repo, *freeargs, **opts)
492 493 finally:
493 494 release(lock, wlock)
494 495
495 496 def _histedit(ui, repo, *freeargs, **opts):
496 497 # TODO only abort if we try and histedit mq patches, not just
497 498 # blanket if mq patches are applied somewhere
498 499 mq = getattr(repo, 'mq', None)
499 500 if mq and mq.applied:
500 501 raise util.Abort(_('source has mq patches applied'))
501 502
502 503 # basic argument incompatibility processing
503 504 outg = opts.get('outgoing')
504 505 cont = opts.get('continue')
505 506 abort = opts.get('abort')
506 507 force = opts.get('force')
507 508 rules = opts.get('commands', '')
508 509 revs = opts.get('rev', [])
509 510 goal = 'new' # This invocation goal, in new, continue, abort
510 511 if force and not outg:
511 512 raise util.Abort(_('--force only allowed with --outgoing'))
512 513 if cont:
513 514 if util.any((outg, abort, revs, freeargs, rules)):
514 515 raise util.Abort(_('no arguments allowed with --continue'))
515 516 goal = 'continue'
516 517 elif abort:
517 518 if util.any((outg, revs, freeargs, rules)):
518 519 raise util.Abort(_('no arguments allowed with --abort'))
519 520 goal = 'abort'
520 521 else:
521 522 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
522 523 raise util.Abort(_('history edit already in progress, try '
523 524 '--continue or --abort'))
524 525 if outg:
525 526 if revs:
526 527 raise util.Abort(_('no revisions allowed with --outgoing'))
527 528 if len(freeargs) > 1:
528 529 raise util.Abort(
529 530 _('only one repo argument allowed with --outgoing'))
530 531 else:
531 532 revs.extend(freeargs)
532 533 if len(revs) != 1:
533 534 raise util.Abort(
534 535 _('histedit requires exactly one ancestor revision'))
535 536
536 537
537 538 if goal == 'continue':
538 539 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
539 540 parentctx = repo[parentctxnode]
540 541 parentctx, repl = bootstrapcontinue(ui, repo, parentctx, rules, opts)
541 542 replacements.extend(repl)
542 543 elif goal == 'abort':
543 544 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
544 545 mapping, tmpnodes, leafs, _ntm = processreplacement(repo, replacements)
545 546 ui.debug('restore wc to old parent %s\n' % node.short(topmost))
546 547 # check whether we should update away
547 548 parentnodes = [c.node() for c in repo[None].parents()]
548 549 for n in leafs | set([parentctxnode]):
549 550 if n in parentnodes:
550 551 hg.clean(repo, topmost)
551 552 break
552 553 else:
553 554 pass
554 555 cleanupnode(ui, repo, 'created', tmpnodes)
555 556 cleanupnode(ui, repo, 'temp', leafs)
556 557 os.unlink(os.path.join(repo.path, 'histedit-state'))
557 558 return
558 559 else:
559 560 cmdutil.checkunfinished(repo)
560 561 cmdutil.bailifchanged(repo)
561 562
562 563 topmost, empty = repo.dirstate.parents()
563 564 if outg:
564 565 if freeargs:
565 566 remote = freeargs[0]
566 567 else:
567 568 remote = None
568 569 root = findoutgoing(ui, repo, remote, force, opts)
569 570 else:
570 rootrevs = list(repo.set('roots(%lr)', revs))
571 if len(rootrevs) != 1:
571 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs)))
572 if len(rr) != 1:
572 573 raise util.Abort(_('The specified revisions must have '
573 574 'exactly one common root'))
574 root = rootrevs[0].node()
575 root = rr[0].node()
575 576
576 577 keep = opts.get('keep', False)
577 578 revs = between(repo, root, topmost, keep)
578 579 if not revs:
579 580 raise util.Abort(_('%s is not an ancestor of working directory') %
580 581 node.short(root))
581 582
582 583 ctxs = [repo[r] for r in revs]
583 584 if not rules:
584 585 rules = '\n'.join([makedesc(c) for c in ctxs])
585 586 rules += '\n\n'
586 587 rules += editcomment % (node.short(root), node.short(topmost))
587 588 rules = ui.edit(rules, ui.username())
588 589 # Save edit rules in .hg/histedit-last-edit.txt in case
589 590 # the user needs to ask for help after something
590 591 # surprising happens.
591 592 f = open(repo.join('histedit-last-edit.txt'), 'w')
592 593 f.write(rules)
593 594 f.close()
594 595 else:
595 596 if rules == '-':
596 597 f = sys.stdin
597 598 else:
598 599 f = open(rules)
599 600 rules = f.read()
600 601 f.close()
601 602 rules = [l for l in (r.strip() for r in rules.splitlines())
602 603 if l and not l[0] == '#']
603 604 rules = verifyrules(rules, repo, ctxs)
604 605
605 606 parentctx = repo[root].parents()[0]
606 607 keep = opts.get('keep', False)
607 608 replacements = []
608 609
609 610
610 611 while rules:
611 612 writestate(repo, parentctx.node(), rules, keep, topmost, replacements)
612 613 action, ha = rules.pop(0)
613 614 ui.debug('histedit: processing %s %s\n' % (action, ha))
614 615 actfunc = actiontable[action]
615 616 parentctx, replacement_ = actfunc(ui, repo, parentctx, ha, opts)
616 617 replacements.extend(replacement_)
617 618
618 619 hg.update(repo, parentctx.node())
619 620
620 621 mapping, tmpnodes, created, ntm = processreplacement(repo, replacements)
621 622 if mapping:
622 623 for prec, succs in mapping.iteritems():
623 624 if not succs:
624 625 ui.debug('histedit: %s is dropped\n' % node.short(prec))
625 626 else:
626 627 ui.debug('histedit: %s is replaced by %s\n' % (
627 628 node.short(prec), node.short(succs[0])))
628 629 if len(succs) > 1:
629 630 m = 'histedit: %s'
630 631 for n in succs[1:]:
631 632 ui.debug(m % node.short(n))
632 633
633 634 if not keep:
634 635 if mapping:
635 636 movebookmarks(ui, repo, mapping, topmost, ntm)
636 637 # TODO update mq state
637 638 if obsolete._enabled:
638 639 markers = []
639 640 # sort by revision number because it sound "right"
640 641 for prec in sorted(mapping, key=repo.changelog.rev):
641 642 succs = mapping[prec]
642 643 markers.append((repo[prec],
643 644 tuple(repo[s] for s in succs)))
644 645 if markers:
645 646 obsolete.createmarkers(repo, markers)
646 647 else:
647 648 cleanupnode(ui, repo, 'replaced', mapping)
648 649
649 650 cleanupnode(ui, repo, 'temp', tmpnodes)
650 651 os.unlink(os.path.join(repo.path, 'histedit-state'))
651 652 if os.path.exists(repo.sjoin('undo')):
652 653 os.unlink(repo.sjoin('undo'))
653 654
654 655 def gatherchildren(repo, ctx):
655 656 # is there any new commit between the expected parent and "."
656 657 #
657 658 # note: does not take non linear new change in account (but previous
658 659 # implementation didn't used them anyway (issue3655)
659 660 newchildren = [c.node() for c in repo.set('(%d::.)', ctx)]
660 661 if ctx.node() != node.nullid:
661 662 if not newchildren:
662 663 # `ctx` should match but no result. This means that
663 664 # currentnode is not a descendant from ctx.
664 665 msg = _('%s is not an ancestor of working directory')
665 666 hint = _('use "histedit --abort" to clear broken state')
666 667 raise util.Abort(msg % ctx, hint=hint)
667 668 newchildren.pop(0) # remove ctx
668 669 return newchildren
669 670
670 671 def bootstrapcontinue(ui, repo, parentctx, rules, opts):
671 672 action, currentnode = rules.pop(0)
672 673 ctx = repo[currentnode]
673 674
674 675 newchildren = gatherchildren(repo, parentctx)
675 676
676 677 # Commit dirty working directory if necessary
677 678 new = None
678 679 m, a, r, d = repo.status()[:4]
679 680 if m or a or r or d:
680 681 # prepare the message for the commit to comes
681 682 if action in ('f', 'fold'):
682 683 message = 'fold-temp-revision %s' % currentnode
683 684 else:
684 685 message = ctx.description()
685 686 editopt = action in ('e', 'edit', 'm', 'mess')
686 687 editor = cmdutil.getcommiteditor(edit=editopt)
687 688 commit = commitfuncfor(repo, ctx)
688 689 new = commit(text=message, user=ctx.user(),
689 690 date=ctx.date(), extra=ctx.extra(),
690 691 editor=editor)
691 692 if new is not None:
692 693 newchildren.append(new)
693 694
694 695 replacements = []
695 696 # track replacements
696 697 if ctx.node() not in newchildren:
697 698 # note: new children may be empty when the changeset is dropped.
698 699 # this happen e.g during conflicting pick where we revert content
699 700 # to parent.
700 701 replacements.append((ctx.node(), tuple(newchildren)))
701 702
702 703 if action in ('f', 'fold'):
703 704 if newchildren:
704 705 # finalize fold operation if applicable
705 706 if new is None:
706 707 new = newchildren[-1]
707 708 else:
708 709 newchildren.pop() # remove new from internal changes
709 710 parentctx, repl = finishfold(ui, repo, parentctx, ctx, new, opts,
710 711 newchildren)
711 712 replacements.extend(repl)
712 713 else:
713 714 # newchildren is empty if the fold did not result in any commit
714 715 # this happen when all folded change are discarded during the
715 716 # merge.
716 717 replacements.append((ctx.node(), (parentctx.node(),)))
717 718 elif newchildren:
718 719 # otherwise update "parentctx" before proceeding to further operation
719 720 parentctx = repo[newchildren[-1]]
720 721 return parentctx, replacements
721 722
722 723
723 724 def between(repo, old, new, keep):
724 725 """select and validate the set of revision to edit
725 726
726 727 When keep is false, the specified set can't have children."""
727 728 ctxs = list(repo.set('%n::%n', old, new))
728 729 if ctxs and not keep:
729 730 if (not obsolete._enabled and
730 731 repo.revs('(%ld::) - (%ld)', ctxs, ctxs)):
731 732 raise util.Abort(_('cannot edit history that would orphan nodes'))
732 733 if repo.revs('(%ld) and merge()', ctxs):
733 734 raise util.Abort(_('cannot edit history that contains merges'))
734 735 root = ctxs[0] # list is already sorted by repo.set
735 736 if not root.phase():
736 737 raise util.Abort(_('cannot edit immutable changeset: %s') % root)
737 738 return [c.node() for c in ctxs]
738 739
739 740
740 741 def writestate(repo, parentnode, rules, keep, topmost, replacements):
741 742 fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
742 743 pickle.dump((parentnode, rules, keep, topmost, replacements), fp)
743 744 fp.close()
744 745
745 746 def readstate(repo):
746 747 """Returns a tuple of (parentnode, rules, keep, topmost, replacements).
747 748 """
748 749 fp = open(os.path.join(repo.path, 'histedit-state'))
749 750 return pickle.load(fp)
750 751
751 752
752 753 def makedesc(c):
753 754 """build a initial action line for a ctx `c`
754 755
755 756 line are in the form:
756 757
757 758 pick <hash> <rev> <summary>
758 759 """
759 760 summary = ''
760 761 if c.description():
761 762 summary = c.description().splitlines()[0]
762 763 line = 'pick %s %d %s' % (c, c.rev(), summary)
763 764 # trim to 80 columns so it's not stupidly wide in my editor
764 765 return util.ellipsis(line, 80)
765 766
766 767 def verifyrules(rules, repo, ctxs):
767 768 """Verify that there exists exactly one edit rule per given changeset.
768 769
769 770 Will abort if there are to many or too few rules, a malformed rule,
770 771 or a rule on a changeset outside of the user-given range.
771 772 """
772 773 parsed = []
773 774 expected = set(str(c) for c in ctxs)
774 775 seen = set()
775 776 for r in rules:
776 777 if ' ' not in r:
777 778 raise util.Abort(_('malformed line "%s"') % r)
778 779 action, rest = r.split(' ', 1)
779 780 ha = rest.strip().split(' ', 1)[0]
780 781 try:
781 782 ha = str(repo[ha]) # ensure its a short hash
782 783 except error.RepoError:
783 784 raise util.Abort(_('unknown changeset %s listed') % ha)
784 785 if ha not in expected:
785 786 raise util.Abort(
786 787 _('may not use changesets other than the ones listed'))
787 788 if ha in seen:
788 789 raise util.Abort(_('duplicated command for changeset %s') % ha)
789 790 seen.add(ha)
790 791 if action not in actiontable:
791 792 raise util.Abort(_('unknown action "%s"') % action)
792 793 parsed.append([action, ha])
793 794 missing = sorted(expected - seen) # sort to stabilize output
794 795 if missing:
795 796 raise util.Abort(_('missing rules for changeset %s') % missing[0],
796 797 hint=_('do you want to use the drop action?'))
797 798 return parsed
798 799
799 800 def processreplacement(repo, replacements):
800 801 """process the list of replacements to return
801 802
802 803 1) the final mapping between original and created nodes
803 804 2) the list of temporary node created by histedit
804 805 3) the list of new commit created by histedit"""
805 806 allsuccs = set()
806 807 replaced = set()
807 808 fullmapping = {}
808 809 # initialise basic set
809 810 # fullmapping record all operation recorded in replacement
810 811 for rep in replacements:
811 812 allsuccs.update(rep[1])
812 813 replaced.add(rep[0])
813 814 fullmapping.setdefault(rep[0], set()).update(rep[1])
814 815 new = allsuccs - replaced
815 816 tmpnodes = allsuccs & replaced
816 817 # Reduce content fullmapping into direct relation between original nodes
817 818 # and final node created during history edition
818 819 # Dropped changeset are replaced by an empty list
819 820 toproceed = set(fullmapping)
820 821 final = {}
821 822 while toproceed:
822 823 for x in list(toproceed):
823 824 succs = fullmapping[x]
824 825 for s in list(succs):
825 826 if s in toproceed:
826 827 # non final node with unknown closure
827 828 # We can't process this now
828 829 break
829 830 elif s in final:
830 831 # non final node, replace with closure
831 832 succs.remove(s)
832 833 succs.update(final[s])
833 834 else:
834 835 final[x] = succs
835 836 toproceed.remove(x)
836 837 # remove tmpnodes from final mapping
837 838 for n in tmpnodes:
838 839 del final[n]
839 840 # we expect all changes involved in final to exist in the repo
840 841 # turn `final` into list (topologically sorted)
841 842 nm = repo.changelog.nodemap
842 843 for prec, succs in final.items():
843 844 final[prec] = sorted(succs, key=nm.get)
844 845
845 846 # computed topmost element (necessary for bookmark)
846 847 if new:
847 848 newtopmost = sorted(new, key=repo.changelog.rev)[-1]
848 849 elif not final:
849 850 # Nothing rewritten at all. we won't need `newtopmost`
850 851 # It is the same as `oldtopmost` and `processreplacement` know it
851 852 newtopmost = None
852 853 else:
853 854 # every body died. The newtopmost is the parent of the root.
854 855 newtopmost = repo[sorted(final, key=repo.changelog.rev)[0]].p1().node()
855 856
856 857 return final, tmpnodes, new, newtopmost
857 858
858 859 def movebookmarks(ui, repo, mapping, oldtopmost, newtopmost):
859 860 """Move bookmark from old to newly created node"""
860 861 if not mapping:
861 862 # if nothing got rewritten there is not purpose for this function
862 863 return
863 864 moves = []
864 865 for bk, old in sorted(repo._bookmarks.iteritems()):
865 866 if old == oldtopmost:
866 867 # special case ensure bookmark stay on tip.
867 868 #
868 869 # This is arguably a feature and we may only want that for the
869 870 # active bookmark. But the behavior is kept compatible with the old
870 871 # version for now.
871 872 moves.append((bk, newtopmost))
872 873 continue
873 874 base = old
874 875 new = mapping.get(base, None)
875 876 if new is None:
876 877 continue
877 878 while not new:
878 879 # base is killed, trying with parent
879 880 base = repo[base].p1().node()
880 881 new = mapping.get(base, (base,))
881 882 # nothing to move
882 883 moves.append((bk, new[-1]))
883 884 if moves:
884 885 marks = repo._bookmarks
885 886 for mark, new in moves:
886 887 old = marks[mark]
887 888 ui.note(_('histedit: moving bookmarks %s from %s to %s\n')
888 889 % (mark, node.short(old), node.short(new)))
889 890 marks[mark] = new
890 891 marks.write()
891 892
892 893 def cleanupnode(ui, repo, name, nodes):
893 894 """strip a group of nodes from the repository
894 895
895 896 The set of node to strip may contains unknown nodes."""
896 897 ui.debug('should strip %s nodes %s\n' %
897 898 (name, ', '.join([node.short(n) for n in nodes])))
898 899 lock = None
899 900 try:
900 901 lock = repo.lock()
901 902 # Find all node that need to be stripped
902 903 # (we hg %lr instead of %ln to silently ignore unknown item
903 904 nm = repo.changelog.nodemap
904 905 nodes = [n for n in nodes if n in nm]
905 906 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
906 907 for c in roots:
907 908 # We should process node in reverse order to strip tip most first.
908 909 # but this trigger a bug in changegroup hook.
909 910 # This would reduce bundle overhead
910 911 repair.strip(ui, repo, c)
911 912 finally:
912 913 release(lock)
913 914
914 915 def summaryhook(ui, repo):
915 916 if not os.path.exists(repo.join('histedit-state')):
916 917 return
917 918 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
918 919 if rules:
919 920 # i18n: column positioning for "hg summary"
920 921 ui.write(_('hist: %s (histedit --continue)\n') %
921 922 (ui.label(_('%d remaining'), 'histedit.remaining') %
922 923 len(rules)))
923 924
924 925 def extsetup(ui):
925 926 cmdutil.summaryhooks.add('histedit', summaryhook)
926 927 cmdutil.unfinishedstates.append(
927 928 ['histedit-state', False, True, _('histedit in progress'),
928 929 _("use 'hg histedit --continue' or 'hg histedit --abort'")])
@@ -1,358 +1,382 b''
1 1 $ . "$TESTDIR/histedit-helpers.sh"
2 2
3 3 $ cat >> $HGRCPATH <<EOF
4 4 > [extensions]
5 5 > histedit=
6 6 > EOF
7 7
8 8 $ initrepo ()
9 9 > {
10 10 > hg init r
11 11 > cd r
12 12 > for x in a b c d e f ; do
13 13 > echo $x > $x
14 14 > hg add $x
15 15 > hg ci -m $x
16 16 > done
17 17 > }
18 18
19 19 $ initrepo
20 20
21 21 log before edit
22 22 $ hg log --graph
23 23 @ changeset: 5:652413bf663e
24 24 | tag: tip
25 25 | user: test
26 26 | date: Thu Jan 01 00:00:00 1970 +0000
27 27 | summary: f
28 28 |
29 29 o changeset: 4:e860deea161a
30 30 | user: test
31 31 | date: Thu Jan 01 00:00:00 1970 +0000
32 32 | summary: e
33 33 |
34 34 o changeset: 3:055a42cdd887
35 35 | user: test
36 36 | date: Thu Jan 01 00:00:00 1970 +0000
37 37 | summary: d
38 38 |
39 39 o changeset: 2:177f92b77385
40 40 | user: test
41 41 | date: Thu Jan 01 00:00:00 1970 +0000
42 42 | summary: c
43 43 |
44 44 o changeset: 1:d2ae7f538514
45 45 | user: test
46 46 | date: Thu Jan 01 00:00:00 1970 +0000
47 47 | summary: b
48 48 |
49 49 o changeset: 0:cb9a9f314b8b
50 50 user: test
51 51 date: Thu Jan 01 00:00:00 1970 +0000
52 52 summary: a
53 53
54 54
55 55 show the edit commands offered
56 56 $ HGEDITOR=cat hg histedit 177f92b77385
57 57 pick 177f92b77385 2 c
58 58 pick 055a42cdd887 3 d
59 59 pick e860deea161a 4 e
60 60 pick 652413bf663e 5 f
61 61
62 62 # Edit history between 177f92b77385 and 652413bf663e
63 63 #
64 64 # Commits are listed from least to most recent
65 65 #
66 66 # Commands:
67 67 # p, pick = use commit
68 68 # e, edit = use commit, but stop for amending
69 69 # f, fold = use commit, but combine it with the one above
70 70 # d, drop = remove commit from history
71 71 # m, mess = edit message without changing commit content
72 72 #
73 73 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
74 74
75 75 edit the history
76 76 (use a hacky editor to check histedit-last-edit.txt backup)
77 77
78 78 $ EDITED="$TESTTMP/editedhistory"
79 79 $ cat > $EDITED <<EOF
80 80 > pick 177f92b77385 c
81 81 > pick e860deea161a e
82 82 > pick 652413bf663e f
83 83 > pick 055a42cdd887 d
84 84 > EOF
85 85 $ HGEDITOR="cat \"$EDITED\" > " hg histedit 177f92b77385 2>&1 | fixbundle
86 86 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
87 87 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
88 88 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
89 89 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
90 90
91 91 rules should end up in .hg/histedit-last-edit.txt:
92 92 $ cat .hg/histedit-last-edit.txt
93 93 pick 177f92b77385 c
94 94 pick e860deea161a e
95 95 pick 652413bf663e f
96 96 pick 055a42cdd887 d
97 97
98 98 log after edit
99 99 $ hg log --graph
100 100 @ changeset: 5:07114f51870f
101 101 | tag: tip
102 102 | user: test
103 103 | date: Thu Jan 01 00:00:00 1970 +0000
104 104 | summary: d
105 105 |
106 106 o changeset: 4:8ade9693061e
107 107 | user: test
108 108 | date: Thu Jan 01 00:00:00 1970 +0000
109 109 | summary: f
110 110 |
111 111 o changeset: 3:d8249471110a
112 112 | user: test
113 113 | date: Thu Jan 01 00:00:00 1970 +0000
114 114 | summary: e
115 115 |
116 116 o changeset: 2:177f92b77385
117 117 | user: test
118 118 | date: Thu Jan 01 00:00:00 1970 +0000
119 119 | summary: c
120 120 |
121 121 o changeset: 1:d2ae7f538514
122 122 | user: test
123 123 | date: Thu Jan 01 00:00:00 1970 +0000
124 124 | summary: b
125 125 |
126 126 o changeset: 0:cb9a9f314b8b
127 127 user: test
128 128 date: Thu Jan 01 00:00:00 1970 +0000
129 129 summary: a
130 130
131 131
132 132 put things back
133 133
134 134 $ hg histedit 177f92b77385 --commands - 2>&1 << EOF | fixbundle
135 135 > pick 177f92b77385 c
136 136 > pick 07114f51870f d
137 137 > pick d8249471110a e
138 138 > pick 8ade9693061e f
139 139 > EOF
140 140 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
141 141 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
142 142 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
143 143 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
144 144
145 145 $ hg log --graph
146 146 @ changeset: 5:7eca9b5b1148
147 147 | tag: tip
148 148 | user: test
149 149 | date: Thu Jan 01 00:00:00 1970 +0000
150 150 | summary: f
151 151 |
152 152 o changeset: 4:915da888f2de
153 153 | user: test
154 154 | date: Thu Jan 01 00:00:00 1970 +0000
155 155 | summary: e
156 156 |
157 157 o changeset: 3:10517e47bbbb
158 158 | user: test
159 159 | date: Thu Jan 01 00:00:00 1970 +0000
160 160 | summary: d
161 161 |
162 162 o changeset: 2:177f92b77385
163 163 | user: test
164 164 | date: Thu Jan 01 00:00:00 1970 +0000
165 165 | summary: c
166 166 |
167 167 o changeset: 1:d2ae7f538514
168 168 | user: test
169 169 | date: Thu Jan 01 00:00:00 1970 +0000
170 170 | summary: b
171 171 |
172 172 o changeset: 0:cb9a9f314b8b
173 173 user: test
174 174 date: Thu Jan 01 00:00:00 1970 +0000
175 175 summary: a
176 176
177 177
178 178 slightly different this time
179 179
180 180 $ hg histedit 177f92b77385 --commands - << EOF 2>&1 | fixbundle
181 181 > pick 10517e47bbbb d
182 182 > pick 7eca9b5b1148 f
183 183 > pick 915da888f2de e
184 184 > pick 177f92b77385 c
185 185 > EOF
186 186 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
187 187 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
188 188 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
189 189 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
190 190 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
191 191 $ hg log --graph
192 192 @ changeset: 5:38b92f448761
193 193 | tag: tip
194 194 | user: test
195 195 | date: Thu Jan 01 00:00:00 1970 +0000
196 196 | summary: c
197 197 |
198 198 o changeset: 4:de71b079d9ce
199 199 | user: test
200 200 | date: Thu Jan 01 00:00:00 1970 +0000
201 201 | summary: e
202 202 |
203 203 o changeset: 3:be9ae3a309c6
204 204 | user: test
205 205 | date: Thu Jan 01 00:00:00 1970 +0000
206 206 | summary: f
207 207 |
208 208 o changeset: 2:799205341b6b
209 209 | user: test
210 210 | date: Thu Jan 01 00:00:00 1970 +0000
211 211 | summary: d
212 212 |
213 213 o changeset: 1:d2ae7f538514
214 214 | user: test
215 215 | date: Thu Jan 01 00:00:00 1970 +0000
216 216 | summary: b
217 217 |
218 218 o changeset: 0:cb9a9f314b8b
219 219 user: test
220 220 date: Thu Jan 01 00:00:00 1970 +0000
221 221 summary: a
222 222
223 223
224 224 keep prevents stripping dead revs
225 225 $ hg histedit 799205341b6b --keep --commands - 2>&1 << EOF | fixbundle
226 226 > pick 799205341b6b d
227 227 > pick be9ae3a309c6 f
228 228 > pick 38b92f448761 c
229 229 > pick de71b079d9ce e
230 230 > EOF
231 231 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
232 232 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
233 233 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
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 abort: may not use changesets other than the ones listed
284 284 $ hg log --graph
285 285 @ changeset: 7:803ef1c6fcfd
286 286 | tag: tip
287 287 | user: test
288 288 | date: Thu Jan 01 00:00:00 1970 +0000
289 289 | summary: e
290 290 |
291 291 o changeset: 6:ece0b8d93dda
292 292 | parent: 3:be9ae3a309c6
293 293 | user: test
294 294 | date: Thu Jan 01 00:00:00 1970 +0000
295 295 | summary: c
296 296 |
297 297 | o changeset: 5:38b92f448761
298 298 | | user: test
299 299 | | date: Thu Jan 01 00:00:00 1970 +0000
300 300 | | summary: c
301 301 | |
302 302 | o changeset: 4:de71b079d9ce
303 303 |/ user: test
304 304 | date: Thu Jan 01 00:00:00 1970 +0000
305 305 | summary: e
306 306 |
307 307 o changeset: 3:be9ae3a309c6
308 308 | user: test
309 309 | date: Thu Jan 01 00:00:00 1970 +0000
310 310 | summary: f
311 311 |
312 312 o changeset: 2:799205341b6b
313 313 | user: test
314 314 | date: Thu Jan 01 00:00:00 1970 +0000
315 315 | summary: d
316 316 |
317 317 o changeset: 1:d2ae7f538514
318 318 | user: test
319 319 | date: Thu Jan 01 00:00:00 1970 +0000
320 320 | summary: b
321 321 |
322 322 o changeset: 0:cb9a9f314b8b
323 323 user: test
324 324 date: Thu Jan 01 00:00:00 1970 +0000
325 325 summary: a
326 326
327 Verify that revsetalias entries work with histedit:
328 $ cat >> $HGRCPATH <<EOF
329 > [revsetalias]
330 > grandparent(ARG) = p1(p1(ARG))
331 > EOF
332 $ echo extra commit >> c
333 $ hg ci -m 'extra commit to c'
334 $ HGEDITOR=cat hg histedit 'grandparent(.)'
335 pick ece0b8d93dda 6 c
336 pick 803ef1c6fcfd 7 e
337 pick 9c863c565126 8 extra commit to c
338
339 # Edit history between ece0b8d93dda and 9c863c565126
340 #
341 # Commits are listed from least to most recent
342 #
343 # Commands:
344 # p, pick = use commit
345 # e, edit = use commit, but stop for amending
346 # f, fold = use commit, but combine it with the one above
347 # d, drop = remove commit from history
348 # m, mess = edit message without changing commit content
349 #
350 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
327 351
328 352 should also work if a commit message is missing
329 353 $ BUNDLE="$TESTDIR/missing-comment.hg"
330 354 $ hg init missing
331 355 $ cd missing
332 356 $ hg unbundle $BUNDLE
333 357 adding changesets
334 358 adding manifests
335 359 adding file changes
336 360 added 3 changesets with 3 changes to 1 files
337 361 (run 'hg update' to get a working copy)
338 362 $ hg co tip
339 363 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
340 364 $ hg log --graph
341 365 @ changeset: 2:bd22688093b3
342 366 | tag: tip
343 367 | user: Robert Altman <robert.altman@telventDTN.com>
344 368 | date: Mon Nov 28 16:40:04 2011 +0000
345 369 | summary: Update file.
346 370 |
347 371 o changeset: 1:3b3e956f9171
348 372 | user: Robert Altman <robert.altman@telventDTN.com>
349 373 | date: Mon Nov 28 16:37:57 2011 +0000
350 374 |
351 375 o changeset: 0:141947992243
352 376 user: Robert Altman <robert.altman@telventDTN.com>
353 377 date: Mon Nov 28 16:35:28 2011 +0000
354 378 summary: Checked in text file
355 379
356 380 $ hg histedit 0
357 381 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
358 382 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now