##// END OF EJS Templates
rebase: don't preserve most extra fields...
Siddharth Agarwal -
r27977:b698abf9 3.7.1 stable
parent child Browse files
Show More
@@ -1,1275 +1,1272
1 1 # rebase.py - rebasing feature for mercurial
2 2 #
3 3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot 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
8 8 '''command to move sets of revisions to a different ancestor
9 9
10 10 This extension lets you rebase changesets in an existing Mercurial
11 11 repository.
12 12
13 13 For more information:
14 14 https://mercurial-scm.org/wiki/RebaseExtension
15 15 '''
16 16
17 17 from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks
18 18 from mercurial import extensions, patch, scmutil, phases, obsolete, error
19 19 from mercurial import copies, repoview, revset
20 20 from mercurial.commands import templateopts
21 21 from mercurial.node import nullrev, nullid, hex, short
22 22 from mercurial.lock import release
23 23 from mercurial.i18n import _
24 24 import os, errno
25 25
26 26 # The following constants are used throughout the rebase module. The ordering of
27 27 # their values must be maintained.
28 28
29 29 # Indicates that a revision needs to be rebased
30 30 revtodo = -1
31 31 nullmerge = -2
32 32 revignored = -3
33 33 # successor in rebase destination
34 34 revprecursor = -4
35 35 # plain prune (no successor)
36 36 revpruned = -5
37 37 revskipped = (revignored, revprecursor, revpruned)
38 38
39 39 cmdtable = {}
40 40 command = cmdutil.command(cmdtable)
41 41 # Note for extension authors: ONLY specify testedwith = 'internal' for
42 42 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
43 43 # be specifying the version(s) of Mercurial they are tested with, or
44 44 # leave the attribute unspecified.
45 45 testedwith = 'internal'
46 46
47 47 def _nothingtorebase():
48 48 return 1
49 49
50 50 def _savegraft(ctx, extra):
51 51 s = ctx.extra().get('source', None)
52 52 if s is not None:
53 53 extra['source'] = s
54 54 s = ctx.extra().get('intermediate-source', None)
55 55 if s is not None:
56 56 extra['intermediate-source'] = s
57 57
58 58 def _savebranch(ctx, extra):
59 59 extra['branch'] = ctx.branch()
60 60
61 61 def _makeextrafn(copiers):
62 62 """make an extrafn out of the given copy-functions.
63 63
64 64 A copy function takes a context and an extra dict, and mutates the
65 65 extra dict as needed based on the given context.
66 66 """
67 67 def extrafn(ctx, extra):
68 68 for c in copiers:
69 69 c(ctx, extra)
70 70 return extrafn
71 71
72 72 def _destrebase(repo):
73 73 # Destination defaults to the latest revision in the
74 74 # current branch
75 75 branch = repo[None].branch()
76 76 return repo[branch].rev()
77 77
78 78 revsetpredicate = revset.extpredicate()
79 79
80 80 @revsetpredicate('_destrebase')
81 81 def _revsetdestrebase(repo, subset, x):
82 82 # ``_rebasedefaultdest()``
83 83
84 84 # default destination for rebase.
85 85 # # XXX: Currently private because I expect the signature to change.
86 86 # # XXX: - taking rev as arguments,
87 87 # # XXX: - bailing out in case of ambiguity vs returning all data.
88 88 # # XXX: - probably merging with the merge destination.
89 89 # i18n: "_rebasedefaultdest" is a keyword
90 90 revset.getargs(x, 0, 0, _("_rebasedefaultdest takes no arguments"))
91 91 return subset & revset.baseset([_destrebase(repo)])
92 92
93 93 @command('rebase',
94 94 [('s', 'source', '',
95 95 _('rebase the specified changeset and descendants'), _('REV')),
96 96 ('b', 'base', '',
97 97 _('rebase everything from branching point of specified changeset'),
98 98 _('REV')),
99 99 ('r', 'rev', [],
100 100 _('rebase these revisions'),
101 101 _('REV')),
102 102 ('d', 'dest', '',
103 103 _('rebase onto the specified changeset'), _('REV')),
104 104 ('', 'collapse', False, _('collapse the rebased changesets')),
105 105 ('m', 'message', '',
106 106 _('use text as collapse commit message'), _('TEXT')),
107 107 ('e', 'edit', False, _('invoke editor on commit messages')),
108 108 ('l', 'logfile', '',
109 109 _('read collapse commit message from file'), _('FILE')),
110 110 ('k', 'keep', False, _('keep original changesets')),
111 111 ('', 'keepbranches', False, _('keep original branch names')),
112 112 ('D', 'detach', False, _('(DEPRECATED)')),
113 113 ('i', 'interactive', False, _('(DEPRECATED)')),
114 114 ('t', 'tool', '', _('specify merge tool')),
115 115 ('c', 'continue', False, _('continue an interrupted rebase')),
116 116 ('a', 'abort', False, _('abort an interrupted rebase'))] +
117 117 templateopts,
118 118 _('[-s REV | -b REV] [-d REV] [OPTION]'))
119 119 def rebase(ui, repo, **opts):
120 120 """move changeset (and descendants) to a different branch
121 121
122 122 Rebase uses repeated merging to graft changesets from one part of
123 123 history (the source) onto another (the destination). This can be
124 124 useful for linearizing *local* changes relative to a master
125 125 development tree.
126 126
127 127 Published commits cannot be rebased (see :hg:`help phases`).
128 128 To copy commits, see :hg:`help graft`.
129 129
130 130 If you don't specify a destination changeset (``-d/--dest``),
131 131 rebase uses the current branch tip as the destination. (The
132 132 destination changeset is not modified by rebasing, but new
133 133 changesets are added as its descendants.)
134 134
135 135 Here are the ways to select changesets:
136 136
137 137 1. Explicitly select them using ``--rev``.
138 138
139 139 2. Use ``--source`` to select a root changeset and include all of its
140 140 descendants.
141 141
142 142 3. Use ``--base`` to select a changeset; rebase will find ancestors
143 143 and their descendants which are not also ancestors of the destination.
144 144
145 145 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
146 146 rebase will use ``--base .`` as above.
147 147
148 148 Rebase will destroy original changesets unless you use ``--keep``.
149 149 It will also move your bookmarks (even if you do).
150 150
151 151 Some changesets may be dropped if they do not contribute changes
152 152 (e.g. merges from the destination branch).
153 153
154 154 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
155 155 a named branch with two heads. You will need to explicitly specify source
156 156 and/or destination.
157 157
158 158 If a rebase is interrupted to manually resolve a conflict, it can be
159 159 continued with --continue/-c or aborted with --abort/-a.
160 160
161 161 .. container:: verbose
162 162
163 163 Examples:
164 164
165 165 - move "local changes" (current commit back to branching point)
166 166 to the current branch tip after a pull::
167 167
168 168 hg rebase
169 169
170 170 - move a single changeset to the stable branch::
171 171
172 172 hg rebase -r 5f493448 -d stable
173 173
174 174 - splice a commit and all its descendants onto another part of history::
175 175
176 176 hg rebase --source c0c3 --dest 4cf9
177 177
178 178 - rebase everything on a branch marked by a bookmark onto the
179 179 default branch::
180 180
181 181 hg rebase --base myfeature --dest default
182 182
183 183 - collapse a sequence of changes into a single commit::
184 184
185 185 hg rebase --collapse -r 1520:1525 -d .
186 186
187 187 - move a named branch while preserving its name::
188 188
189 189 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
190 190
191 191 Returns 0 on success, 1 if nothing to rebase or there are
192 192 unresolved conflicts.
193 193
194 194 """
195 195 originalwd = target = None
196 196 activebookmark = None
197 197 external = nullrev
198 198 # Mapping between the old revision id and either what is the new rebased
199 199 # revision or what needs to be done with the old revision. The state dict
200 200 # will be what contains most of the rebase progress state.
201 201 state = {}
202 202 skipped = set()
203 203 targetancestors = set()
204 204
205 205
206 206 lock = wlock = None
207 207 try:
208 208 wlock = repo.wlock()
209 209 lock = repo.lock()
210 210
211 211 # Validate input and define rebasing points
212 212 destf = opts.get('dest', None)
213 213 srcf = opts.get('source', None)
214 214 basef = opts.get('base', None)
215 215 revf = opts.get('rev', [])
216 216 contf = opts.get('continue')
217 217 abortf = opts.get('abort')
218 218 collapsef = opts.get('collapse', False)
219 219 collapsemsg = cmdutil.logmessage(ui, opts)
220 220 date = opts.get('date', None)
221 221 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
222 222 extrafns = [_savegraft]
223 223 if e:
224 224 extrafns = [e]
225 225 keepf = opts.get('keep', False)
226 226 keepbranchesf = opts.get('keepbranches', False)
227 227 # keepopen is not meant for use on the command line, but by
228 228 # other extensions
229 229 keepopen = opts.get('keepopen', False)
230 230
231 231 if opts.get('interactive'):
232 232 try:
233 233 if extensions.find('histedit'):
234 234 enablehistedit = ''
235 235 except KeyError:
236 236 enablehistedit = " --config extensions.histedit="
237 237 help = "hg%s help -e histedit" % enablehistedit
238 238 msg = _("interactive history editing is supported by the "
239 239 "'histedit' extension (see \"%s\")") % help
240 240 raise error.Abort(msg)
241 241
242 242 if collapsemsg and not collapsef:
243 243 raise error.Abort(
244 244 _('message can only be specified with collapse'))
245 245
246 246 if contf or abortf:
247 247 if contf and abortf:
248 248 raise error.Abort(_('cannot use both abort and continue'))
249 249 if collapsef:
250 250 raise error.Abort(
251 251 _('cannot use collapse with continue or abort'))
252 252 if srcf or basef or destf:
253 253 raise error.Abort(
254 254 _('abort and continue do not allow specifying revisions'))
255 255 if abortf and opts.get('tool', False):
256 256 ui.warn(_('tool option will be ignored\n'))
257 257
258 258 try:
259 259 (originalwd, target, state, skipped, collapsef, keepf,
260 260 keepbranchesf, external, activebookmark) = restorestatus(repo)
261 261 except error.RepoLookupError:
262 262 if abortf:
263 263 clearstatus(repo)
264 264 repo.ui.warn(_('rebase aborted (no revision is removed,'
265 265 ' only broken state is cleared)\n'))
266 266 return 0
267 267 else:
268 268 msg = _('cannot continue inconsistent rebase')
269 269 hint = _('use "hg rebase --abort" to clear broken state')
270 270 raise error.Abort(msg, hint=hint)
271 271 if abortf:
272 272 return abort(repo, originalwd, target, state,
273 273 activebookmark=activebookmark)
274 274 else:
275 275 if srcf and basef:
276 276 raise error.Abort(_('cannot specify both a '
277 277 'source and a base'))
278 278 if revf and basef:
279 279 raise error.Abort(_('cannot specify both a '
280 280 'revision and a base'))
281 281 if revf and srcf:
282 282 raise error.Abort(_('cannot specify both a '
283 283 'revision and a source'))
284 284
285 285 cmdutil.checkunfinished(repo)
286 286 cmdutil.bailifchanged(repo)
287 287
288 288 if destf:
289 289 dest = scmutil.revsingle(repo, destf)
290 290 else:
291 291 dest = repo[_destrebase(repo)]
292 292 destf = str(dest)
293 293
294 294 if revf:
295 295 rebaseset = scmutil.revrange(repo, revf)
296 296 if not rebaseset:
297 297 ui.status(_('empty "rev" revision set - '
298 298 'nothing to rebase\n'))
299 299 return _nothingtorebase()
300 300 elif srcf:
301 301 src = scmutil.revrange(repo, [srcf])
302 302 if not src:
303 303 ui.status(_('empty "source" revision set - '
304 304 'nothing to rebase\n'))
305 305 return _nothingtorebase()
306 306 rebaseset = repo.revs('(%ld)::', src)
307 307 assert rebaseset
308 308 else:
309 309 base = scmutil.revrange(repo, [basef or '.'])
310 310 if not base:
311 311 ui.status(_('empty "base" revision set - '
312 312 "can't compute rebase set\n"))
313 313 return _nothingtorebase()
314 314 commonanc = repo.revs('ancestor(%ld, %d)', base, dest).first()
315 315 if commonanc is not None:
316 316 rebaseset = repo.revs('(%d::(%ld) - %d)::',
317 317 commonanc, base, commonanc)
318 318 else:
319 319 rebaseset = []
320 320
321 321 if not rebaseset:
322 322 # transform to list because smartsets are not comparable to
323 323 # lists. This should be improved to honor laziness of
324 324 # smartset.
325 325 if list(base) == [dest.rev()]:
326 326 if basef:
327 327 ui.status(_('nothing to rebase - %s is both "base"'
328 328 ' and destination\n') % dest)
329 329 else:
330 330 ui.status(_('nothing to rebase - working directory '
331 331 'parent is also destination\n'))
332 332 elif not repo.revs('%ld - ::%d', base, dest):
333 333 if basef:
334 334 ui.status(_('nothing to rebase - "base" %s is '
335 335 'already an ancestor of destination '
336 336 '%s\n') %
337 337 ('+'.join(str(repo[r]) for r in base),
338 338 dest))
339 339 else:
340 340 ui.status(_('nothing to rebase - working '
341 341 'directory parent is already an '
342 342 'ancestor of destination %s\n') % dest)
343 343 else: # can it happen?
344 344 ui.status(_('nothing to rebase from %s to %s\n') %
345 345 ('+'.join(str(repo[r]) for r in base), dest))
346 346 return _nothingtorebase()
347 347
348 348 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
349 349 if (not (keepf or allowunstable)
350 350 and repo.revs('first(children(%ld) - %ld)',
351 351 rebaseset, rebaseset)):
352 352 raise error.Abort(
353 353 _("can't remove original changesets with"
354 354 " unrebased descendants"),
355 355 hint=_('use --keep to keep original changesets'))
356 356
357 357 obsoletenotrebased = {}
358 358 if ui.configbool('experimental', 'rebaseskipobsolete'):
359 359 rebasesetrevs = set(rebaseset)
360 360 rebaseobsrevs = _filterobsoleterevs(repo, rebasesetrevs)
361 361 obsoletenotrebased = _computeobsoletenotrebased(repo,
362 362 rebaseobsrevs,
363 363 dest)
364 364 rebaseobsskipped = set(obsoletenotrebased)
365 365
366 366 # Obsolete node with successors not in dest leads to divergence
367 367 divergenceok = ui.configbool('rebase',
368 368 'allowdivergence')
369 369 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
370 370
371 371 if divergencebasecandidates and not divergenceok:
372 372 msg = _("this rebase will cause divergence")
373 373 h = _("to force the rebase please set "
374 374 "rebase.allowdivergence=True")
375 375 raise error.Abort(msg, hint=h)
376 376
377 377 # - plain prune (no successor) changesets are rebased
378 378 # - split changesets are not rebased if at least one of the
379 379 # changeset resulting from the split is an ancestor of dest
380 380 rebaseset = rebasesetrevs - rebaseobsskipped
381 381 if rebasesetrevs and not rebaseset:
382 382 msg = _('all requested changesets have equivalents '
383 383 'or were marked as obsolete')
384 384 hint = _('to force the rebase, set the config '
385 385 'experimental.rebaseskipobsolete to False')
386 386 raise error.Abort(msg, hint=hint)
387 387
388 388 result = buildstate(repo, dest, rebaseset, collapsef,
389 389 obsoletenotrebased)
390 390
391 391 if not result:
392 392 # Empty state built, nothing to rebase
393 393 ui.status(_('nothing to rebase\n'))
394 394 return _nothingtorebase()
395 395
396 396 root = min(rebaseset)
397 397 if not keepf and not repo[root].mutable():
398 398 raise error.Abort(_("can't rebase public changeset %s")
399 399 % repo[root],
400 400 hint=_('see "hg help phases" for details'))
401 401
402 402 originalwd, target, state = result
403 403 if collapsef:
404 404 targetancestors = repo.changelog.ancestors([target],
405 405 inclusive=True)
406 406 external = externalparent(repo, state, targetancestors)
407 407
408 408 if dest.closesbranch() and not keepbranchesf:
409 409 ui.status(_('reopening closed branch head %s\n') % dest)
410 410
411 411 if keepbranchesf:
412 412 # insert _savebranch at the start of extrafns so if
413 413 # there's a user-provided extrafn it can clobber branch if
414 414 # desired
415 415 extrafns.insert(0, _savebranch)
416 416 if collapsef:
417 417 branches = set()
418 418 for rev in state:
419 419 branches.add(repo[rev].branch())
420 420 if len(branches) > 1:
421 421 raise error.Abort(_('cannot collapse multiple named '
422 422 'branches'))
423 423
424 424 # Rebase
425 425 if not targetancestors:
426 426 targetancestors = repo.changelog.ancestors([target], inclusive=True)
427 427
428 428 # Keep track of the current bookmarks in order to reset them later
429 429 currentbookmarks = repo._bookmarks.copy()
430 430 activebookmark = activebookmark or repo._activebookmark
431 431 if activebookmark:
432 432 bookmarks.deactivate(repo)
433 433
434 434 extrafn = _makeextrafn(extrafns)
435 435
436 436 sortedstate = sorted(state)
437 437 total = len(sortedstate)
438 438 pos = 0
439 439 for rev in sortedstate:
440 440 ctx = repo[rev]
441 441 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
442 442 ctx.description().split('\n', 1)[0])
443 443 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
444 444 if names:
445 445 desc += ' (%s)' % ' '.join(names)
446 446 pos += 1
447 447 if state[rev] == revtodo:
448 448 ui.status(_('rebasing %s\n') % desc)
449 449 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
450 450 _('changesets'), total)
451 451 p1, p2, base = defineparents(repo, rev, target, state,
452 452 targetancestors)
453 453 storestatus(repo, originalwd, target, state, collapsef, keepf,
454 454 keepbranchesf, external, activebookmark)
455 455 if len(repo[None].parents()) == 2:
456 456 repo.ui.debug('resuming interrupted rebase\n')
457 457 else:
458 458 try:
459 459 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
460 460 'rebase')
461 461 stats = rebasenode(repo, rev, p1, base, state,
462 462 collapsef, target)
463 463 if stats and stats[3] > 0:
464 464 raise error.InterventionRequired(
465 465 _('unresolved conflicts (see hg '
466 466 'resolve, then hg rebase --continue)'))
467 467 finally:
468 468 ui.setconfig('ui', 'forcemerge', '', 'rebase')
469 469 if not collapsef:
470 470 merging = p2 != nullrev
471 471 editform = cmdutil.mergeeditform(merging, 'rebase')
472 472 editor = cmdutil.getcommiteditor(editform=editform, **opts)
473 473 newnode = concludenode(repo, rev, p1, p2, extrafn=extrafn,
474 474 editor=editor,
475 475 keepbranches=keepbranchesf,
476 476 date=date)
477 477 else:
478 478 # Skip commit if we are collapsing
479 479 repo.dirstate.beginparentchange()
480 480 repo.setparents(repo[p1].node())
481 481 repo.dirstate.endparentchange()
482 482 newnode = None
483 483 # Update the state
484 484 if newnode is not None:
485 485 state[rev] = repo[newnode].rev()
486 486 ui.debug('rebased as %s\n' % short(newnode))
487 487 else:
488 488 if not collapsef:
489 489 ui.warn(_('note: rebase of %d:%s created no changes '
490 490 'to commit\n') % (rev, ctx))
491 491 skipped.add(rev)
492 492 state[rev] = p1
493 493 ui.debug('next revision set to %s\n' % p1)
494 494 elif state[rev] == nullmerge:
495 495 ui.debug('ignoring null merge rebase of %s\n' % rev)
496 496 elif state[rev] == revignored:
497 497 ui.status(_('not rebasing ignored %s\n') % desc)
498 498 elif state[rev] == revprecursor:
499 499 targetctx = repo[obsoletenotrebased[rev]]
500 500 desctarget = '%d:%s "%s"' % (targetctx.rev(), targetctx,
501 501 targetctx.description().split('\n', 1)[0])
502 502 msg = _('note: not rebasing %s, already in destination as %s\n')
503 503 ui.status(msg % (desc, desctarget))
504 504 elif state[rev] == revpruned:
505 505 msg = _('note: not rebasing %s, it has no successor\n')
506 506 ui.status(msg % desc)
507 507 else:
508 508 ui.status(_('already rebased %s as %s\n') %
509 509 (desc, repo[state[rev]]))
510 510
511 511 ui.progress(_('rebasing'), None)
512 512 ui.note(_('rebase merging completed\n'))
513 513
514 514 if collapsef and not keepopen:
515 515 p1, p2, _base = defineparents(repo, min(state), target,
516 516 state, targetancestors)
517 517 editopt = opts.get('edit')
518 518 editform = 'rebase.collapse'
519 519 if collapsemsg:
520 520 commitmsg = collapsemsg
521 521 else:
522 522 commitmsg = 'Collapsed revision'
523 523 for rebased in state:
524 524 if rebased not in skipped and state[rebased] > nullmerge:
525 525 commitmsg += '\n* %s' % repo[rebased].description()
526 526 editopt = True
527 527 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
528 528 newnode = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
529 529 extrafn=extrafn, editor=editor,
530 530 keepbranches=keepbranchesf,
531 531 date=date)
532 532 if newnode is None:
533 533 newrev = target
534 534 else:
535 535 newrev = repo[newnode].rev()
536 536 for oldrev in state.iterkeys():
537 537 if state[oldrev] > nullmerge:
538 538 state[oldrev] = newrev
539 539
540 540 if 'qtip' in repo.tags():
541 541 updatemq(repo, state, skipped, **opts)
542 542
543 543 if currentbookmarks:
544 544 # Nodeids are needed to reset bookmarks
545 545 nstate = {}
546 546 for k, v in state.iteritems():
547 547 if v > nullmerge:
548 548 nstate[repo[k].node()] = repo[v].node()
549 549 # XXX this is the same as dest.node() for the non-continue path --
550 550 # this should probably be cleaned up
551 551 targetnode = repo[target].node()
552 552
553 553 # restore original working directory
554 554 # (we do this before stripping)
555 555 newwd = state.get(originalwd, originalwd)
556 556 if newwd < 0:
557 557 # original directory is a parent of rebase set root or ignored
558 558 newwd = originalwd
559 559 if newwd not in [c.rev() for c in repo[None].parents()]:
560 560 ui.note(_("update back to initial working directory parent\n"))
561 561 hg.updaterepo(repo, newwd, False)
562 562
563 563 if not keepf:
564 564 collapsedas = None
565 565 if collapsef:
566 566 collapsedas = newnode
567 567 clearrebased(ui, repo, state, skipped, collapsedas)
568 568
569 569 with repo.transaction('bookmark') as tr:
570 570 if currentbookmarks:
571 571 updatebookmarks(repo, targetnode, nstate, currentbookmarks, tr)
572 572 if activebookmark not in repo._bookmarks:
573 573 # active bookmark was divergent one and has been deleted
574 574 activebookmark = None
575 575 clearstatus(repo)
576 576
577 577 ui.note(_("rebase completed\n"))
578 578 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
579 579 if skipped:
580 580 ui.note(_("%d revisions have been skipped\n") % len(skipped))
581 581
582 582 if (activebookmark and
583 583 repo['.'].node() == repo._bookmarks[activebookmark]):
584 584 bookmarks.activate(repo, activebookmark)
585 585
586 586 finally:
587 587 release(lock, wlock)
588 588
589 589 def externalparent(repo, state, targetancestors):
590 590 """Return the revision that should be used as the second parent
591 591 when the revisions in state is collapsed on top of targetancestors.
592 592 Abort if there is more than one parent.
593 593 """
594 594 parents = set()
595 595 source = min(state)
596 596 for rev in state:
597 597 if rev == source:
598 598 continue
599 599 for p in repo[rev].parents():
600 600 if (p.rev() not in state
601 601 and p.rev() not in targetancestors):
602 602 parents.add(p.rev())
603 603 if not parents:
604 604 return nullrev
605 605 if len(parents) == 1:
606 606 return parents.pop()
607 607 raise error.Abort(_('unable to collapse on top of %s, there is more '
608 608 'than one external parent: %s') %
609 609 (max(targetancestors),
610 610 ', '.join(str(p) for p in sorted(parents))))
611 611
612 612 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
613 613 keepbranches=False, date=None):
614 614 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
615 615 but also store useful information in extra.
616 616 Return node of committed revision.'''
617 617 dsguard = cmdutil.dirstateguard(repo, 'rebase')
618 618 try:
619 619 repo.setparents(repo[p1].node(), repo[p2].node())
620 620 ctx = repo[rev]
621 621 if commitmsg is None:
622 622 commitmsg = ctx.description()
623 623 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
624 extra = ctx.extra().copy()
625 if not keepbranches:
626 del extra['branch']
627 extra['rebase_source'] = ctx.hex()
624 extra = {'rebase_source': ctx.hex()}
628 625 if extrafn:
629 626 extrafn(ctx, extra)
630 627
631 628 backup = repo.ui.backupconfig('phases', 'new-commit')
632 629 try:
633 630 targetphase = max(ctx.phase(), phases.draft)
634 631 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
635 632 if keepbranch:
636 633 repo.ui.setconfig('ui', 'allowemptycommit', True)
637 634 # Commit might fail if unresolved files exist
638 635 if date is None:
639 636 date = ctx.date()
640 637 newnode = repo.commit(text=commitmsg, user=ctx.user(),
641 638 date=date, extra=extra, editor=editor)
642 639 finally:
643 640 repo.ui.restoreconfig(backup)
644 641
645 642 repo.dirstate.setbranch(repo[newnode].branch())
646 643 dsguard.close()
647 644 return newnode
648 645 finally:
649 646 release(dsguard)
650 647
651 648 def rebasenode(repo, rev, p1, base, state, collapse, target):
652 649 'Rebase a single revision rev on top of p1 using base as merge ancestor'
653 650 # Merge phase
654 651 # Update to target and merge it with local
655 652 if repo['.'].rev() != p1:
656 653 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
657 654 merge.update(repo, p1, False, True)
658 655 else:
659 656 repo.ui.debug(" already in target\n")
660 657 repo.dirstate.write(repo.currenttransaction())
661 658 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
662 659 if base is not None:
663 660 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
664 661 # When collapsing in-place, the parent is the common ancestor, we
665 662 # have to allow merging with it.
666 663 stats = merge.update(repo, rev, True, True, base, collapse,
667 664 labels=['dest', 'source'])
668 665 if collapse:
669 666 copies.duplicatecopies(repo, rev, target)
670 667 else:
671 668 # If we're not using --collapse, we need to
672 669 # duplicate copies between the revision we're
673 670 # rebasing and its first parent, but *not*
674 671 # duplicate any copies that have already been
675 672 # performed in the destination.
676 673 p1rev = repo[rev].p1().rev()
677 674 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
678 675 return stats
679 676
680 677 def nearestrebased(repo, rev, state):
681 678 """return the nearest ancestors of rev in the rebase result"""
682 679 rebased = [r for r in state if state[r] > nullmerge]
683 680 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
684 681 if candidates:
685 682 return state[candidates.first()]
686 683 else:
687 684 return None
688 685
689 686 def defineparents(repo, rev, target, state, targetancestors):
690 687 'Return the new parent relationship of the revision that will be rebased'
691 688 parents = repo[rev].parents()
692 689 p1 = p2 = nullrev
693 690
694 691 p1n = parents[0].rev()
695 692 if p1n in targetancestors:
696 693 p1 = target
697 694 elif p1n in state:
698 695 if state[p1n] == nullmerge:
699 696 p1 = target
700 697 elif state[p1n] in revskipped:
701 698 p1 = nearestrebased(repo, p1n, state)
702 699 if p1 is None:
703 700 p1 = target
704 701 else:
705 702 p1 = state[p1n]
706 703 else: # p1n external
707 704 p1 = target
708 705 p2 = p1n
709 706
710 707 if len(parents) == 2 and parents[1].rev() not in targetancestors:
711 708 p2n = parents[1].rev()
712 709 # interesting second parent
713 710 if p2n in state:
714 711 if p1 == target: # p1n in targetancestors or external
715 712 p1 = state[p2n]
716 713 elif state[p2n] in revskipped:
717 714 p2 = nearestrebased(repo, p2n, state)
718 715 if p2 is None:
719 716 # no ancestors rebased yet, detach
720 717 p2 = target
721 718 else:
722 719 p2 = state[p2n]
723 720 else: # p2n external
724 721 if p2 != nullrev: # p1n external too => rev is a merged revision
725 722 raise error.Abort(_('cannot use revision %d as base, result '
726 723 'would have 3 parents') % rev)
727 724 p2 = p2n
728 725 repo.ui.debug(" future parents are %d and %d\n" %
729 726 (repo[p1].rev(), repo[p2].rev()))
730 727
731 728 if not any(p.rev() in state for p in parents):
732 729 # Case (1) root changeset of a non-detaching rebase set.
733 730 # Let the merge mechanism find the base itself.
734 731 base = None
735 732 elif not repo[rev].p2():
736 733 # Case (2) detaching the node with a single parent, use this parent
737 734 base = repo[rev].p1().rev()
738 735 else:
739 736 # Assuming there is a p1, this is the case where there also is a p2.
740 737 # We are thus rebasing a merge and need to pick the right merge base.
741 738 #
742 739 # Imagine we have:
743 740 # - M: current rebase revision in this step
744 741 # - A: one parent of M
745 742 # - B: other parent of M
746 743 # - D: destination of this merge step (p1 var)
747 744 #
748 745 # Consider the case where D is a descendant of A or B and the other is
749 746 # 'outside'. In this case, the right merge base is the D ancestor.
750 747 #
751 748 # An informal proof, assuming A is 'outside' and B is the D ancestor:
752 749 #
753 750 # If we pick B as the base, the merge involves:
754 751 # - changes from B to M (actual changeset payload)
755 752 # - changes from B to D (induced by rebase) as D is a rebased
756 753 # version of B)
757 754 # Which exactly represent the rebase operation.
758 755 #
759 756 # If we pick A as the base, the merge involves:
760 757 # - changes from A to M (actual changeset payload)
761 758 # - changes from A to D (with include changes between unrelated A and B
762 759 # plus changes induced by rebase)
763 760 # Which does not represent anything sensible and creates a lot of
764 761 # conflicts. A is thus not the right choice - B is.
765 762 #
766 763 # Note: The base found in this 'proof' is only correct in the specified
767 764 # case. This base does not make sense if is not D a descendant of A or B
768 765 # or if the other is not parent 'outside' (especially not if the other
769 766 # parent has been rebased). The current implementation does not
770 767 # make it feasible to consider different cases separately. In these
771 768 # other cases we currently just leave it to the user to correctly
772 769 # resolve an impossible merge using a wrong ancestor.
773 770 for p in repo[rev].parents():
774 771 if state.get(p.rev()) == p1:
775 772 base = p.rev()
776 773 break
777 774 else: # fallback when base not found
778 775 base = None
779 776
780 777 # Raise because this function is called wrong (see issue 4106)
781 778 raise AssertionError('no base found to rebase on '
782 779 '(defineparents called wrong)')
783 780 return p1, p2, base
784 781
785 782 def isagitpatch(repo, patchname):
786 783 'Return true if the given patch is in git format'
787 784 mqpatch = os.path.join(repo.mq.path, patchname)
788 785 for line in patch.linereader(file(mqpatch, 'rb')):
789 786 if line.startswith('diff --git'):
790 787 return True
791 788 return False
792 789
793 790 def updatemq(repo, state, skipped, **opts):
794 791 'Update rebased mq patches - finalize and then import them'
795 792 mqrebase = {}
796 793 mq = repo.mq
797 794 original_series = mq.fullseries[:]
798 795 skippedpatches = set()
799 796
800 797 for p in mq.applied:
801 798 rev = repo[p.node].rev()
802 799 if rev in state:
803 800 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
804 801 (rev, p.name))
805 802 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
806 803 else:
807 804 # Applied but not rebased, not sure this should happen
808 805 skippedpatches.add(p.name)
809 806
810 807 if mqrebase:
811 808 mq.finish(repo, mqrebase.keys())
812 809
813 810 # We must start import from the newest revision
814 811 for rev in sorted(mqrebase, reverse=True):
815 812 if rev not in skipped:
816 813 name, isgit = mqrebase[rev]
817 814 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
818 815 (name, state[rev], repo[state[rev]]))
819 816 mq.qimport(repo, (), patchname=name, git=isgit,
820 817 rev=[str(state[rev])])
821 818 else:
822 819 # Rebased and skipped
823 820 skippedpatches.add(mqrebase[rev][0])
824 821
825 822 # Patches were either applied and rebased and imported in
826 823 # order, applied and removed or unapplied. Discard the removed
827 824 # ones while preserving the original series order and guards.
828 825 newseries = [s for s in original_series
829 826 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
830 827 mq.fullseries[:] = newseries
831 828 mq.seriesdirty = True
832 829 mq.savedirty()
833 830
834 831 def updatebookmarks(repo, targetnode, nstate, originalbookmarks, tr):
835 832 'Move bookmarks to their correct changesets, and delete divergent ones'
836 833 marks = repo._bookmarks
837 834 for k, v in originalbookmarks.iteritems():
838 835 if v in nstate:
839 836 # update the bookmarks for revs that have moved
840 837 marks[k] = nstate[v]
841 838 bookmarks.deletedivergent(repo, [targetnode], k)
842 839 marks.recordchange(tr)
843 840
844 841 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
845 842 external, activebookmark):
846 843 'Store the current status to allow recovery'
847 844 f = repo.vfs("rebasestate", "w")
848 845 f.write(repo[originalwd].hex() + '\n')
849 846 f.write(repo[target].hex() + '\n')
850 847 f.write(repo[external].hex() + '\n')
851 848 f.write('%d\n' % int(collapse))
852 849 f.write('%d\n' % int(keep))
853 850 f.write('%d\n' % int(keepbranches))
854 851 f.write('%s\n' % (activebookmark or ''))
855 852 for d, v in state.iteritems():
856 853 oldrev = repo[d].hex()
857 854 if v >= 0:
858 855 newrev = repo[v].hex()
859 856 elif v == revtodo:
860 857 # To maintain format compatibility, we have to use nullid.
861 858 # Please do remove this special case when upgrading the format.
862 859 newrev = hex(nullid)
863 860 else:
864 861 newrev = v
865 862 f.write("%s:%s\n" % (oldrev, newrev))
866 863 f.close()
867 864 repo.ui.debug('rebase status stored\n')
868 865
869 866 def clearstatus(repo):
870 867 'Remove the status files'
871 868 _clearrebasesetvisibiliy(repo)
872 869 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
873 870
874 871 def restorestatus(repo):
875 872 'Restore a previously stored status'
876 873 keepbranches = None
877 874 target = None
878 875 collapse = False
879 876 external = nullrev
880 877 activebookmark = None
881 878 state = {}
882 879
883 880 try:
884 881 f = repo.vfs("rebasestate")
885 882 for i, l in enumerate(f.read().splitlines()):
886 883 if i == 0:
887 884 originalwd = repo[l].rev()
888 885 elif i == 1:
889 886 target = repo[l].rev()
890 887 elif i == 2:
891 888 external = repo[l].rev()
892 889 elif i == 3:
893 890 collapse = bool(int(l))
894 891 elif i == 4:
895 892 keep = bool(int(l))
896 893 elif i == 5:
897 894 keepbranches = bool(int(l))
898 895 elif i == 6 and not (len(l) == 81 and ':' in l):
899 896 # line 6 is a recent addition, so for backwards compatibility
900 897 # check that the line doesn't look like the oldrev:newrev lines
901 898 activebookmark = l
902 899 else:
903 900 oldrev, newrev = l.split(':')
904 901 if newrev in (str(nullmerge), str(revignored),
905 902 str(revprecursor), str(revpruned)):
906 903 state[repo[oldrev].rev()] = int(newrev)
907 904 elif newrev == nullid:
908 905 state[repo[oldrev].rev()] = revtodo
909 906 # Legacy compat special case
910 907 else:
911 908 state[repo[oldrev].rev()] = repo[newrev].rev()
912 909
913 910 except IOError as err:
914 911 if err.errno != errno.ENOENT:
915 912 raise
916 913 raise error.Abort(_('no rebase in progress'))
917 914
918 915 if keepbranches is None:
919 916 raise error.Abort(_('.hg/rebasestate is incomplete'))
920 917
921 918 skipped = set()
922 919 # recompute the set of skipped revs
923 920 if not collapse:
924 921 seen = set([target])
925 922 for old, new in sorted(state.items()):
926 923 if new != revtodo and new in seen:
927 924 skipped.add(old)
928 925 seen.add(new)
929 926 repo.ui.debug('computed skipped revs: %s\n' %
930 927 (' '.join(str(r) for r in sorted(skipped)) or None))
931 928 repo.ui.debug('rebase status resumed\n')
932 929 _setrebasesetvisibility(repo, state.keys())
933 930 return (originalwd, target, state, skipped,
934 931 collapse, keep, keepbranches, external, activebookmark)
935 932
936 933 def needupdate(repo, state):
937 934 '''check whether we should `update --clean` away from a merge, or if
938 935 somehow the working dir got forcibly updated, e.g. by older hg'''
939 936 parents = [p.rev() for p in repo[None].parents()]
940 937
941 938 # Are we in a merge state at all?
942 939 if len(parents) < 2:
943 940 return False
944 941
945 942 # We should be standing on the first as-of-yet unrebased commit.
946 943 firstunrebased = min([old for old, new in state.iteritems()
947 944 if new == nullrev])
948 945 if firstunrebased in parents:
949 946 return True
950 947
951 948 return False
952 949
953 950 def abort(repo, originalwd, target, state, activebookmark=None):
954 951 '''Restore the repository to its original state. Additional args:
955 952
956 953 activebookmark: the name of the bookmark that should be active after the
957 954 restore'''
958 955
959 956 try:
960 957 # If the first commits in the rebased set get skipped during the rebase,
961 958 # their values within the state mapping will be the target rev id. The
962 959 # dstates list must must not contain the target rev (issue4896)
963 960 dstates = [s for s in state.values() if s >= 0 and s != target]
964 961 immutable = [d for d in dstates if not repo[d].mutable()]
965 962 cleanup = True
966 963 if immutable:
967 964 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
968 965 % ', '.join(str(repo[r]) for r in immutable),
969 966 hint=_('see "hg help phases" for details'))
970 967 cleanup = False
971 968
972 969 descendants = set()
973 970 if dstates:
974 971 descendants = set(repo.changelog.descendants(dstates))
975 972 if descendants - set(dstates):
976 973 repo.ui.warn(_("warning: new changesets detected on target branch, "
977 974 "can't strip\n"))
978 975 cleanup = False
979 976
980 977 if cleanup:
981 978 # Update away from the rebase if necessary
982 979 if needupdate(repo, state):
983 980 merge.update(repo, originalwd, False, True)
984 981
985 982 # Strip from the first rebased revision
986 983 rebased = filter(lambda x: x >= 0 and x != target, state.values())
987 984 if rebased:
988 985 strippoints = [
989 986 c.node() for c in repo.set('roots(%ld)', rebased)]
990 987 # no backup of rebased cset versions needed
991 988 repair.strip(repo.ui, repo, strippoints)
992 989
993 990 if activebookmark and activebookmark in repo._bookmarks:
994 991 bookmarks.activate(repo, activebookmark)
995 992
996 993 finally:
997 994 clearstatus(repo)
998 995 repo.ui.warn(_('rebase aborted\n'))
999 996 return 0
1000 997
1001 998 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1002 999 '''Define which revisions are going to be rebased and where
1003 1000
1004 1001 repo: repo
1005 1002 dest: context
1006 1003 rebaseset: set of rev
1007 1004 '''
1008 1005 _setrebasesetvisibility(repo, rebaseset)
1009 1006
1010 1007 # This check isn't strictly necessary, since mq detects commits over an
1011 1008 # applied patch. But it prevents messing up the working directory when
1012 1009 # a partially completed rebase is blocked by mq.
1013 1010 if 'qtip' in repo.tags() and (dest.node() in
1014 1011 [s.node for s in repo.mq.applied]):
1015 1012 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1016 1013
1017 1014 roots = list(repo.set('roots(%ld)', rebaseset))
1018 1015 if not roots:
1019 1016 raise error.Abort(_('no matching revisions'))
1020 1017 roots.sort()
1021 1018 state = {}
1022 1019 detachset = set()
1023 1020 for root in roots:
1024 1021 commonbase = root.ancestor(dest)
1025 1022 if commonbase == root:
1026 1023 raise error.Abort(_('source is ancestor of destination'))
1027 1024 if commonbase == dest:
1028 1025 samebranch = root.branch() == dest.branch()
1029 1026 if not collapse and samebranch and root in dest.children():
1030 1027 repo.ui.debug('source is a child of destination\n')
1031 1028 return None
1032 1029
1033 1030 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, root))
1034 1031 state.update(dict.fromkeys(rebaseset, revtodo))
1035 1032 # Rebase tries to turn <dest> into a parent of <root> while
1036 1033 # preserving the number of parents of rebased changesets:
1037 1034 #
1038 1035 # - A changeset with a single parent will always be rebased as a
1039 1036 # changeset with a single parent.
1040 1037 #
1041 1038 # - A merge will be rebased as merge unless its parents are both
1042 1039 # ancestors of <dest> or are themselves in the rebased set and
1043 1040 # pruned while rebased.
1044 1041 #
1045 1042 # If one parent of <root> is an ancestor of <dest>, the rebased
1046 1043 # version of this parent will be <dest>. This is always true with
1047 1044 # --base option.
1048 1045 #
1049 1046 # Otherwise, we need to *replace* the original parents with
1050 1047 # <dest>. This "detaches" the rebased set from its former location
1051 1048 # and rebases it onto <dest>. Changes introduced by ancestors of
1052 1049 # <root> not common with <dest> (the detachset, marked as
1053 1050 # nullmerge) are "removed" from the rebased changesets.
1054 1051 #
1055 1052 # - If <root> has a single parent, set it to <dest>.
1056 1053 #
1057 1054 # - If <root> is a merge, we cannot decide which parent to
1058 1055 # replace, the rebase operation is not clearly defined.
1059 1056 #
1060 1057 # The table below sums up this behavior:
1061 1058 #
1062 1059 # +------------------+----------------------+-------------------------+
1063 1060 # | | one parent | merge |
1064 1061 # +------------------+----------------------+-------------------------+
1065 1062 # | parent in | new parent is <dest> | parents in ::<dest> are |
1066 1063 # | ::<dest> | | remapped to <dest> |
1067 1064 # +------------------+----------------------+-------------------------+
1068 1065 # | unrelated source | new parent is <dest> | ambiguous, abort |
1069 1066 # +------------------+----------------------+-------------------------+
1070 1067 #
1071 1068 # The actual abort is handled by `defineparents`
1072 1069 if len(root.parents()) <= 1:
1073 1070 # ancestors of <root> not ancestors of <dest>
1074 1071 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1075 1072 [root.rev()]))
1076 1073 for r in detachset:
1077 1074 if r not in state:
1078 1075 state[r] = nullmerge
1079 1076 if len(roots) > 1:
1080 1077 # If we have multiple roots, we may have "hole" in the rebase set.
1081 1078 # Rebase roots that descend from those "hole" should not be detached as
1082 1079 # other root are. We use the special `revignored` to inform rebase that
1083 1080 # the revision should be ignored but that `defineparents` should search
1084 1081 # a rebase destination that make sense regarding rebased topology.
1085 1082 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1086 1083 for ignored in set(rebasedomain) - set(rebaseset):
1087 1084 state[ignored] = revignored
1088 1085 for r in obsoletenotrebased:
1089 1086 if obsoletenotrebased[r] is None:
1090 1087 state[r] = revpruned
1091 1088 else:
1092 1089 state[r] = revprecursor
1093 1090 return repo['.'].rev(), dest.rev(), state
1094 1091
1095 1092 def clearrebased(ui, repo, state, skipped, collapsedas=None):
1096 1093 """dispose of rebased revision at the end of the rebase
1097 1094
1098 1095 If `collapsedas` is not None, the rebase was a collapse whose result if the
1099 1096 `collapsedas` node."""
1100 1097 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1101 1098 markers = []
1102 1099 for rev, newrev in sorted(state.items()):
1103 1100 if newrev >= 0:
1104 1101 if rev in skipped:
1105 1102 succs = ()
1106 1103 elif collapsedas is not None:
1107 1104 succs = (repo[collapsedas],)
1108 1105 else:
1109 1106 succs = (repo[newrev],)
1110 1107 markers.append((repo[rev], succs))
1111 1108 if markers:
1112 1109 obsolete.createmarkers(repo, markers)
1113 1110 else:
1114 1111 rebased = [rev for rev in state if state[rev] > nullmerge]
1115 1112 if rebased:
1116 1113 stripped = []
1117 1114 for root in repo.set('roots(%ld)', rebased):
1118 1115 if set(repo.changelog.descendants([root.rev()])) - set(state):
1119 1116 ui.warn(_("warning: new changesets detected "
1120 1117 "on source branch, not stripping\n"))
1121 1118 else:
1122 1119 stripped.append(root.node())
1123 1120 if stripped:
1124 1121 # backup the old csets by default
1125 1122 repair.strip(ui, repo, stripped, "all")
1126 1123
1127 1124
1128 1125 def pullrebase(orig, ui, repo, *args, **opts):
1129 1126 'Call rebase after pull if the latter has been invoked with --rebase'
1130 1127 ret = None
1131 1128 if opts.get('rebase'):
1132 1129 wlock = lock = None
1133 1130 try:
1134 1131 wlock = repo.wlock()
1135 1132 lock = repo.lock()
1136 1133 if opts.get('update'):
1137 1134 del opts['update']
1138 1135 ui.debug('--update and --rebase are not compatible, ignoring '
1139 1136 'the update flag\n')
1140 1137
1141 1138 movemarkfrom = repo['.'].node()
1142 1139 revsprepull = len(repo)
1143 1140 origpostincoming = commands.postincoming
1144 1141 def _dummy(*args, **kwargs):
1145 1142 pass
1146 1143 commands.postincoming = _dummy
1147 1144 try:
1148 1145 ret = orig(ui, repo, *args, **opts)
1149 1146 finally:
1150 1147 commands.postincoming = origpostincoming
1151 1148 revspostpull = len(repo)
1152 1149 if revspostpull > revsprepull:
1153 1150 # --rev option from pull conflict with rebase own --rev
1154 1151 # dropping it
1155 1152 if 'rev' in opts:
1156 1153 del opts['rev']
1157 1154 # positional argument from pull conflicts with rebase's own
1158 1155 # --source.
1159 1156 if 'source' in opts:
1160 1157 del opts['source']
1161 1158 rebase(ui, repo, **opts)
1162 1159 branch = repo[None].branch()
1163 1160 dest = repo[branch].rev()
1164 1161 if dest != repo['.'].rev():
1165 1162 # there was nothing to rebase we force an update
1166 1163 hg.update(repo, dest)
1167 1164 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
1168 1165 ui.status(_("updating bookmark %s\n")
1169 1166 % repo._activebookmark)
1170 1167 finally:
1171 1168 release(lock, wlock)
1172 1169 else:
1173 1170 if opts.get('tool'):
1174 1171 raise error.Abort(_('--tool can only be used with --rebase'))
1175 1172 ret = orig(ui, repo, *args, **opts)
1176 1173
1177 1174 return ret
1178 1175
1179 1176 def _setrebasesetvisibility(repo, revs):
1180 1177 """store the currently rebased set on the repo object
1181 1178
1182 1179 This is used by another function to prevent rebased revision to because
1183 1180 hidden (see issue4505)"""
1184 1181 repo = repo.unfiltered()
1185 1182 revs = set(revs)
1186 1183 repo._rebaseset = revs
1187 1184 # invalidate cache if visibility changes
1188 1185 hiddens = repo.filteredrevcache.get('visible', set())
1189 1186 if revs & hiddens:
1190 1187 repo.invalidatevolatilesets()
1191 1188
1192 1189 def _clearrebasesetvisibiliy(repo):
1193 1190 """remove rebaseset data from the repo"""
1194 1191 repo = repo.unfiltered()
1195 1192 if '_rebaseset' in vars(repo):
1196 1193 del repo._rebaseset
1197 1194
1198 1195 def _rebasedvisible(orig, repo):
1199 1196 """ensure rebased revs stay visible (see issue4505)"""
1200 1197 blockers = orig(repo)
1201 1198 blockers.update(getattr(repo, '_rebaseset', ()))
1202 1199 return blockers
1203 1200
1204 1201 def _filterobsoleterevs(repo, revs):
1205 1202 """returns a set of the obsolete revisions in revs"""
1206 1203 return set(r for r in revs if repo[r].obsolete())
1207 1204
1208 1205 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1209 1206 """return a mapping obsolete => successor for all obsolete nodes to be
1210 1207 rebased that have a successors in the destination
1211 1208
1212 1209 obsolete => None entries in the mapping indicate nodes with no succesor"""
1213 1210 obsoletenotrebased = {}
1214 1211
1215 1212 # Build a mapping successor => obsolete nodes for the obsolete
1216 1213 # nodes to be rebased
1217 1214 allsuccessors = {}
1218 1215 cl = repo.changelog
1219 1216 for r in rebaseobsrevs:
1220 1217 node = cl.node(r)
1221 1218 for s in obsolete.allsuccessors(repo.obsstore, [node]):
1222 1219 try:
1223 1220 allsuccessors[cl.rev(s)] = cl.rev(node)
1224 1221 except LookupError:
1225 1222 pass
1226 1223
1227 1224 if allsuccessors:
1228 1225 # Look for successors of obsolete nodes to be rebased among
1229 1226 # the ancestors of dest
1230 1227 ancs = cl.ancestors([repo[dest].rev()],
1231 1228 stoprev=min(allsuccessors),
1232 1229 inclusive=True)
1233 1230 for s in allsuccessors:
1234 1231 if s in ancs:
1235 1232 obsoletenotrebased[allsuccessors[s]] = s
1236 1233 elif (s == allsuccessors[s] and
1237 1234 allsuccessors.values().count(s) == 1):
1238 1235 # plain prune
1239 1236 obsoletenotrebased[s] = None
1240 1237
1241 1238 return obsoletenotrebased
1242 1239
1243 1240 def summaryhook(ui, repo):
1244 1241 if not os.path.exists(repo.join('rebasestate')):
1245 1242 return
1246 1243 try:
1247 1244 state = restorestatus(repo)[2]
1248 1245 except error.RepoLookupError:
1249 1246 # i18n: column positioning for "hg summary"
1250 1247 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1251 1248 ui.write(msg)
1252 1249 return
1253 1250 numrebased = len([i for i in state.itervalues() if i >= 0])
1254 1251 # i18n: column positioning for "hg summary"
1255 1252 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1256 1253 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1257 1254 ui.label(_('%d remaining'), 'rebase.remaining') %
1258 1255 (len(state) - numrebased)))
1259 1256
1260 1257 def uisetup(ui):
1261 1258 #Replace pull with a decorator to provide --rebase option
1262 1259 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1263 1260 entry[1].append(('', 'rebase', None,
1264 1261 _("rebase working directory to branch head")))
1265 1262 entry[1].append(('t', 'tool', '',
1266 1263 _("specify merge tool for rebase")))
1267 1264 cmdutil.summaryhooks.add('rebase', summaryhook)
1268 1265 cmdutil.unfinishedstates.append(
1269 1266 ['rebasestate', False, False, _('rebase in progress'),
1270 1267 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1271 1268 cmdutil.afterresolvedstates.append(
1272 1269 ['rebasestate', _('hg rebase --continue')])
1273 1270 # ensure rebased rev are not hidden
1274 1271 extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
1275 1272 revsetpredicate.setup()
@@ -1,443 +1,435
1 1 $ . "$TESTDIR/histedit-helpers.sh"
2 2
3 3 Enable obsolete
4 4
5 5 $ cat >> $HGRCPATH << EOF
6 6 > [ui]
7 7 > logtemplate= {rev}:{node|short} {desc|firstline}
8 8 > [phases]
9 9 > publish=False
10 10 > [experimental]
11 11 > evolution=createmarkers,allowunstable
12 12 > [extensions]
13 13 > histedit=
14 14 > rebase=
15 15 > EOF
16 16
17 17 $ hg init base
18 18 $ cd base
19 19
20 20 $ for x in a b c d e f ; do
21 21 > echo $x > $x
22 22 > hg add $x
23 23 > hg ci -m $x
24 24 > done
25 25
26 26 $ hg log --graph
27 27 @ 5:652413bf663e f
28 28 |
29 29 o 4:e860deea161a e
30 30 |
31 31 o 3:055a42cdd887 d
32 32 |
33 33 o 2:177f92b77385 c
34 34 |
35 35 o 1:d2ae7f538514 b
36 36 |
37 37 o 0:cb9a9f314b8b a
38 38
39 39
40 40 $ HGEDITOR=cat hg histedit 1
41 41 pick d2ae7f538514 1 b
42 42 pick 177f92b77385 2 c
43 43 pick 055a42cdd887 3 d
44 44 pick e860deea161a 4 e
45 45 pick 652413bf663e 5 f
46 46
47 47 # Edit history between d2ae7f538514 and 652413bf663e
48 48 #
49 49 # Commits are listed from least to most recent
50 50 #
51 51 # Commands:
52 52 #
53 53 # e, edit = use commit, but stop for amending
54 54 # m, mess = edit commit message without changing commit content
55 55 # p, pick = use commit
56 56 # d, drop = remove commit from history
57 57 # f, fold = use commit, but combine it with the one above
58 58 # r, roll = like fold, but discard this commit's description
59 59 #
60 60 $ hg histedit 1 --commands - --verbose <<EOF | grep histedit
61 61 > pick 177f92b77385 2 c
62 62 > drop d2ae7f538514 1 b
63 63 > pick 055a42cdd887 3 d
64 64 > fold e860deea161a 4 e
65 65 > pick 652413bf663e 5 f
66 66 > EOF
67 67 [1]
68 68 $ hg log --graph --hidden
69 69 @ 10:cacdfd884a93 f
70 70 |
71 71 o 9:59d9f330561f d
72 72 |
73 73 | x 8:b558abc46d09 fold-temp-revision e860deea161a
74 74 | |
75 75 | x 7:96e494a2d553 d
76 76 |/
77 77 o 6:b346ab9a313d c
78 78 |
79 79 | x 5:652413bf663e f
80 80 | |
81 81 | x 4:e860deea161a e
82 82 | |
83 83 | x 3:055a42cdd887 d
84 84 | |
85 85 | x 2:177f92b77385 c
86 86 | |
87 87 | x 1:d2ae7f538514 b
88 88 |/
89 89 o 0:cb9a9f314b8b a
90 90
91 91 $ hg debugobsolete
92 92 96e494a2d553dd05902ba1cee1d94d4cb7b8faed 0 {b346ab9a313db8537ecf96fca3ca3ca984ef3bd7} (*) {'user': 'test'} (glob)
93 93 b558abc46d09c30f57ac31e85a8a3d64d2e906e4 0 {96e494a2d553dd05902ba1cee1d94d4cb7b8faed} (*) {'user': 'test'} (glob)
94 94 d2ae7f538514cd87c17547b0de4cea71fe1af9fb 0 {cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b} (*) {'user': 'test'} (glob)
95 95 177f92b773850b59254aa5e923436f921b55483b b346ab9a313db8537ecf96fca3ca3ca984ef3bd7 0 (*) {'user': 'test'} (glob)
96 96 055a42cdd88768532f9cf79daa407fc8d138de9b 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob)
97 97 e860deea161a2f77de56603b340ebbb4536308ae 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob)
98 98 652413bf663ef2a641cab26574e46d5f5a64a55a cacdfd884a9321ec4e1de275ef3949fa953a1f83 0 (*) {'user': 'test'} (glob)
99 99
100 100
101 101 Ensure hidden revision does not prevent histedit
102 102 -------------------------------------------------
103 103
104 104 create an hidden revision
105 105
106 106 $ hg histedit 6 --commands - << EOF
107 107 > pick b346ab9a313d 6 c
108 108 > drop 59d9f330561f 7 d
109 109 > pick cacdfd884a93 8 f
110 110 > EOF
111 111 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
112 112 $ hg log --graph
113 113 @ 11:c13eb81022ca f
114 114 |
115 115 o 6:b346ab9a313d c
116 116 |
117 117 o 0:cb9a9f314b8b a
118 118
119 119 check hidden revision are ignored (6 have hidden children 7 and 8)
120 120
121 121 $ hg histedit 6 --commands - << EOF
122 122 > pick b346ab9a313d 6 c
123 123 > pick c13eb81022ca 8 f
124 124 > EOF
125 125
126 126
127 127
128 128 Test that rewriting leaving instability behind is allowed
129 129 ---------------------------------------------------------------------
130 130
131 131 $ hg up '.^'
132 132 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
133 133 $ hg log -r 'children(.)'
134 134 11:c13eb81022ca f (no-eol)
135 135 $ hg histedit -r '.' --commands - <<EOF
136 136 > edit b346ab9a313d 6 c
137 137 > EOF
138 138 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
139 139 adding c
140 140 Editing (b346ab9a313d), you may commit or record as needed now.
141 141 (hg histedit --continue to resume)
142 142 [1]
143 143 $ echo c >> c
144 144 $ hg histedit --continue
145 145
146 146 $ hg log -r 'unstable()'
147 147 11:c13eb81022ca f (no-eol)
148 148
149 149 stabilise
150 150
151 151 $ hg rebase -r 'unstable()' -d .
152 152 rebasing 11:c13eb81022ca "f"
153 153 $ hg up tip -q
154 154
155 check that extra has accumulated from histedit and rebase
156
157 $ hg log -T '{extras % "{key}={value}\n"}\n' -r tip
158 branch=default
159 histedit_source=cacdfd884a9321ec4e1de275ef3949fa953a1f83
160 rebase_source=c13eb81022caa686a369223fe7f926bc4f7db576
161
162
163 155 Test dropping of changeset on the top of the stack
164 156 -------------------------------------------------------
165 157
166 158 Nothing is rewritten below, the working directory parent must be change for the
167 159 dropped changeset to be hidden.
168 160
169 161 $ cd ..
170 162 $ hg clone base droplast
171 163 updating to branch default
172 164 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
173 165 $ cd droplast
174 166 $ hg histedit -r '40db8afa467b' --commands - << EOF
175 167 > pick 40db8afa467b 10 c
176 > drop 947ece25170f 11 f
168 > drop b449568bf7fc 11 f
177 169 > EOF
178 170 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
179 171 $ hg log -G
180 172 @ 12:40db8afa467b c
181 173 |
182 174 o 0:cb9a9f314b8b a
183 175
184 176
185 177 With rewritten ancestors
186 178
187 179 $ echo e > e
188 180 $ hg add e
189 181 $ hg commit -m g
190 182 $ echo f > f
191 183 $ hg add f
192 184 $ hg commit -m h
193 185 $ hg histedit -r '40db8afa467b' --commands - << EOF
194 186 > pick 47a8561c0449 12 g
195 187 > pick 40db8afa467b 10 c
196 188 > drop 1b3b05f35ff0 13 h
197 189 > EOF
198 190 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
199 191 $ hg log -G
200 192 @ 17:ee6544123ab8 c
201 193 |
202 194 o 16:269e713e9eae g
203 195 |
204 196 o 0:cb9a9f314b8b a
205 197
206 198 $ cd ../base
207 199
208 200
209 201
210 202 Test phases support
211 203 ===========================================
212 204
213 205 Check that histedit respect immutability
214 206 -------------------------------------------
215 207
216 208 $ cat >> $HGRCPATH << EOF
217 209 > [ui]
218 210 > logtemplate= {rev}:{node|short} ({phase}) {desc|firstline}\n
219 211 > EOF
220 212
221 213 $ hg ph -pv '.^'
222 214 phase changed for 2 changesets
223 215 $ hg log -G
224 @ 13:947ece25170f (draft) f
216 @ 13:b449568bf7fc (draft) f
225 217 |
226 218 o 12:40db8afa467b (public) c
227 219 |
228 220 o 0:cb9a9f314b8b (public) a
229 221
230 222 $ hg histedit -r '.~2'
231 223 abort: cannot edit public changeset: cb9a9f314b8b
232 224 (see "hg help phases" for details)
233 225 [255]
234 226
235 227
236 228 Prepare further testing
237 229 -------------------------------------------
238 230
239 231 $ for x in g h i j k ; do
240 232 > echo $x > $x
241 233 > hg add $x
242 234 > hg ci -m $x
243 235 > done
244 236 $ hg phase --force --secret .~2
245 237 $ hg log -G
246 @ 18:14bda137d5b3 (secret) k
238 @ 18:ee118ab9fa44 (secret) k
247 239 |
248 o 17:c62e7241a4f2 (secret) j
240 o 17:3a6c53ee7f3d (secret) j
249 241 |
250 o 16:9cd3934e05af (secret) i
242 o 16:b605fb7503f2 (secret) i
251 243 |
252 o 15:ee4a24fc4dfa (draft) h
244 o 15:7395e1ff83bd (draft) h
253 245 |
254 o 14:d22905de3528 (draft) g
246 o 14:6b70183d2492 (draft) g
255 247 |
256 o 13:947ece25170f (draft) f
248 o 13:b449568bf7fc (draft) f
257 249 |
258 250 o 12:40db8afa467b (public) c
259 251 |
260 252 o 0:cb9a9f314b8b (public) a
261 253
262 254 $ cd ..
263 255
264 256 simple phase conservation
265 257 -------------------------------------------
266 258
267 259 Resulting changeset should conserve the phase of the original one whatever the
268 260 phases.new-commit option is.
269 261
270 262 New-commit as draft (default)
271 263
272 264 $ cp -r base simple-draft
273 265 $ cd simple-draft
274 $ hg histedit -r '947ece25170f' --commands - << EOF
275 > edit 947ece25170f 11 f
276 > pick d22905de3528 12 g
277 > pick ee4a24fc4dfa 13 h
278 > pick 9cd3934e05af 14 i
279 > pick c62e7241a4f2 15 j
280 > pick 14bda137d5b3 16 k
266 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
267 > edit b449568bf7fc 11 f
268 > pick 6b70183d2492 12 g
269 > pick 7395e1ff83bd 13 h
270 > pick b605fb7503f2 14 i
271 > pick 3a6c53ee7f3d 15 j
272 > pick ee118ab9fa44 16 k
281 273 > EOF
282 274 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
283 275 adding f
284 Editing (947ece25170f), you may commit or record as needed now.
276 Editing (b449568bf7fc), you may commit or record as needed now.
285 277 (hg histedit --continue to resume)
286 278 [1]
287 279 $ echo f >> f
288 280 $ hg histedit --continue
289 281 $ hg log -G
290 @ 24:12925f763c90 (secret) k
282 @ 24:12e89af74238 (secret) k
291 283 |
292 o 23:4545a6e77442 (secret) j
284 o 23:636a8687b22e (secret) j
293 285 |
294 o 22:d947a0798e76 (secret) i
286 o 22:ccaf0a38653f (secret) i
295 287 |
296 o 21:28fb35ae4ebb (draft) h
288 o 21:11a89d1c2613 (draft) h
297 289 |
298 o 20:10b22a5a9645 (draft) g
290 o 20:c1dec7ca82ea (draft) g
299 291 |
300 o 19:c5a1db4a69f5 (draft) f
292 o 19:087281e68428 (draft) f
301 293 |
302 294 o 12:40db8afa467b (public) c
303 295 |
304 296 o 0:cb9a9f314b8b (public) a
305 297
306 298 $ cd ..
307 299
308 300
309 301 New-commit as draft (default)
310 302
311 303 $ cp -r base simple-secret
312 304 $ cd simple-secret
313 305 $ cat >> .hg/hgrc << EOF
314 306 > [phases]
315 307 > new-commit=secret
316 308 > EOF
317 $ hg histedit -r '947ece25170f' --commands - << EOF
318 > edit 947ece25170f 11 f
319 > pick d22905de3528 12 g
320 > pick ee4a24fc4dfa 13 h
321 > pick 9cd3934e05af 14 i
322 > pick c62e7241a4f2 15 j
323 > pick 14bda137d5b3 16 k
309 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
310 > edit b449568bf7fc 11 f
311 > pick 6b70183d2492 12 g
312 > pick 7395e1ff83bd 13 h
313 > pick b605fb7503f2 14 i
314 > pick 3a6c53ee7f3d 15 j
315 > pick ee118ab9fa44 16 k
324 316 > EOF
325 317 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
326 318 adding f
327 Editing (947ece25170f), you may commit or record as needed now.
319 Editing (b449568bf7fc), you may commit or record as needed now.
328 320 (hg histedit --continue to resume)
329 321 [1]
330 322 $ echo f >> f
331 323 $ hg histedit --continue
332 324 $ hg log -G
333 @ 24:12925f763c90 (secret) k
325 @ 24:12e89af74238 (secret) k
334 326 |
335 o 23:4545a6e77442 (secret) j
327 o 23:636a8687b22e (secret) j
336 328 |
337 o 22:d947a0798e76 (secret) i
329 o 22:ccaf0a38653f (secret) i
338 330 |
339 o 21:28fb35ae4ebb (draft) h
331 o 21:11a89d1c2613 (draft) h
340 332 |
341 o 20:10b22a5a9645 (draft) g
333 o 20:c1dec7ca82ea (draft) g
342 334 |
343 o 19:c5a1db4a69f5 (draft) f
335 o 19:087281e68428 (draft) f
344 336 |
345 337 o 12:40db8afa467b (public) c
346 338 |
347 339 o 0:cb9a9f314b8b (public) a
348 340
349 341 $ cd ..
350 342
351 343
352 344 Changeset reordering
353 345 -------------------------------------------
354 346
355 347 If a secret changeset is put before a draft one, all descendant should be secret.
356 348 It seems more important to present the secret phase.
357 349
358 350 $ cp -r base reorder
359 351 $ cd reorder
360 $ hg histedit -r '947ece25170f' --commands - << EOF
361 > pick 947ece25170f 11 f
362 > pick c62e7241a4f2 15 j
363 > pick d22905de3528 12 g
364 > pick 9cd3934e05af 14 i
365 > pick ee4a24fc4dfa 13 h
366 > pick 14bda137d5b3 16 k
352 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
353 > pick b449568bf7fc 11 f
354 > pick 3a6c53ee7f3d 15 j
355 > pick 6b70183d2492 12 g
356 > pick b605fb7503f2 14 i
357 > pick 7395e1ff83bd 13 h
358 > pick ee118ab9fa44 16 k
367 359 > EOF
368 360 0 files updated, 0 files merged, 5 files removed, 0 files unresolved
369 361 $ hg log -G
370 @ 23:9e712162b2c1 (secret) k
362 @ 23:558246857888 (secret) k
371 363 |
372 o 22:490861543602 (secret) h
364 o 22:28bd44768535 (secret) h
373 365 |
374 o 21:86aeda50b70d (secret) i
366 o 21:d5395202aeb9 (secret) i
375 367 |
376 o 20:b2fa360bc090 (secret) g
368 o 20:21edda8e341b (secret) g
377 369 |
378 o 19:e10fb4e3eb8e (secret) j
370 o 19:5ab64f3a4832 (secret) j
379 371 |
380 o 13:947ece25170f (draft) f
372 o 13:b449568bf7fc (draft) f
381 373 |
382 374 o 12:40db8afa467b (public) c
383 375 |
384 376 o 0:cb9a9f314b8b (public) a
385 377
386 378 $ cd ..
387 379
388 380 Changeset folding
389 381 -------------------------------------------
390 382
391 383 Folding a secret changeset with a draft one turn the result secret (again,
392 384 better safe than sorry). Folding between same phase changeset still works
393 385
394 386 Note that there is a few reordering in this series for more extensive test
395 387
396 388 $ cp -r base folding
397 389 $ cd folding
398 390 $ cat >> .hg/hgrc << EOF
399 391 > [phases]
400 392 > new-commit=secret
401 393 > EOF
402 $ hg histedit -r '947ece25170f' --commands - << EOF
403 > pick ee4a24fc4dfa 13 h
404 > fold 947ece25170f 11 f
405 > pick d22905de3528 12 g
406 > fold c62e7241a4f2 15 j
407 > pick 9cd3934e05af 14 i
408 > fold 14bda137d5b3 16 k
394 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
395 > pick 7395e1ff83bd 13 h
396 > fold b449568bf7fc 11 f
397 > pick 6b70183d2492 12 g
398 > fold 3a6c53ee7f3d 15 j
399 > pick b605fb7503f2 14 i
400 > fold ee118ab9fa44 16 k
409 401 > EOF
410 402 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
411 403 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
412 404 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
413 405 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
414 406 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
415 407 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
416 408 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
417 409 $ hg log -G
418 @ 27:769e8ee8708e (secret) i
410 @ 27:f9daec13fb98 (secret) i
419 411 |
420 o 24:3de6dbab1b62 (secret) g
412 o 24:49807617f46a (secret) g
421 413 |
422 o 21:1d51647632b2 (draft) h
414 o 21:050280826e04 (draft) h
423 415 |
424 416 o 12:40db8afa467b (public) c
425 417 |
426 418 o 0:cb9a9f314b8b (public) a
427 419
428 $ hg co 3de6dbab1b62
420 $ hg co 49807617f46a
429 421 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
430 422 $ echo wat >> wat
431 423 $ hg add wat
432 424 $ hg ci -m 'add wat'
433 425 created new head
434 $ hg merge 769e8ee8708e
426 $ hg merge f9daec13fb98
435 427 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
436 428 (branch merge, don't forget to commit)
437 429 $ hg ci -m 'merge'
438 430 $ echo not wat > wat
439 431 $ hg ci -m 'modify wat'
440 $ hg histedit 1d51647632b2
432 $ hg histedit 050280826e04
441 433 abort: cannot edit history that contains merges
442 434 [255]
443 435 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now