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