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