##// END OF EJS Templates
rebase: ignore negative state when updating back to original wc parent...
Pierre-Yves David -
r23440:57d35d3c stable
parent child Browse files
Show More
@@ -1,1029 +1,1032
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 http://mercurial.selenic.com/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
20 20 from mercurial.commands import templateopts
21 21 from mercurial.node import nullrev
22 22 from mercurial.lock import release
23 23 from mercurial.i18n import _
24 24 import os, errno
25 25
26 26 nullmerge = -2
27 27 revignored = -3
28 28
29 29 cmdtable = {}
30 30 command = cmdutil.command(cmdtable)
31 31 testedwith = 'internal'
32 32
33 33 def _savegraft(ctx, extra):
34 34 s = ctx.extra().get('source', None)
35 35 if s is not None:
36 36 extra['source'] = s
37 37
38 38 def _savebranch(ctx, extra):
39 39 extra['branch'] = ctx.branch()
40 40
41 41 def _makeextrafn(copiers):
42 42 """make an extrafn out of the given copy-functions.
43 43
44 44 A copy function takes a context and an extra dict, and mutates the
45 45 extra dict as needed based on the given context.
46 46 """
47 47 def extrafn(ctx, extra):
48 48 for c in copiers:
49 49 c(ctx, extra)
50 50 return extrafn
51 51
52 52 @command('rebase',
53 53 [('s', 'source', '',
54 54 _('rebase the specified changeset and descendants'), _('REV')),
55 55 ('b', 'base', '',
56 56 _('rebase everything from branching point of specified changeset'),
57 57 _('REV')),
58 58 ('r', 'rev', [],
59 59 _('rebase these revisions'),
60 60 _('REV')),
61 61 ('d', 'dest', '',
62 62 _('rebase onto the specified changeset'), _('REV')),
63 63 ('', 'collapse', False, _('collapse the rebased changesets')),
64 64 ('m', 'message', '',
65 65 _('use text as collapse commit message'), _('TEXT')),
66 66 ('e', 'edit', False, _('invoke editor on commit messages')),
67 67 ('l', 'logfile', '',
68 68 _('read collapse commit message from file'), _('FILE')),
69 69 ('', 'keep', False, _('keep original changesets')),
70 70 ('', 'keepbranches', False, _('keep original branch names')),
71 71 ('D', 'detach', False, _('(DEPRECATED)')),
72 72 ('i', 'interactive', False, _('(DEPRECATED)')),
73 73 ('t', 'tool', '', _('specify merge tool')),
74 74 ('c', 'continue', False, _('continue an interrupted rebase')),
75 75 ('a', 'abort', False, _('abort an interrupted rebase'))] +
76 76 templateopts,
77 77 _('[-s REV | -b REV] [-d REV] [OPTION]'))
78 78 def rebase(ui, repo, **opts):
79 79 """move changeset (and descendants) to a different branch
80 80
81 81 Rebase uses repeated merging to graft changesets from one part of
82 82 history (the source) onto another (the destination). This can be
83 83 useful for linearizing *local* changes relative to a master
84 84 development tree.
85 85
86 86 You should not rebase changesets that have already been shared
87 87 with others. Doing so will force everybody else to perform the
88 88 same rebase or they will end up with duplicated changesets after
89 89 pulling in your rebased changesets.
90 90
91 91 In its default configuration, Mercurial will prevent you from
92 92 rebasing published changes. See :hg:`help phases` for details.
93 93
94 94 If you don't specify a destination changeset (``-d/--dest``),
95 95 rebase uses the current branch tip as the destination. (The
96 96 destination changeset is not modified by rebasing, but new
97 97 changesets are added as its descendants.)
98 98
99 99 You can specify which changesets to rebase in two ways: as a
100 100 "source" changeset or as a "base" changeset. Both are shorthand
101 101 for a topologically related set of changesets (the "source
102 102 branch"). If you specify source (``-s/--source``), rebase will
103 103 rebase that changeset and all of its descendants onto dest. If you
104 104 specify base (``-b/--base``), rebase will select ancestors of base
105 105 back to but not including the common ancestor with dest. Thus,
106 106 ``-b`` is less precise but more convenient than ``-s``: you can
107 107 specify any changeset in the source branch, and rebase will select
108 108 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
109 109 uses the parent of the working directory as the base.
110 110
111 111 For advanced usage, a third way is available through the ``--rev``
112 112 option. It allows you to specify an arbitrary set of changesets to
113 113 rebase. Descendants of revs you specify with this option are not
114 114 automatically included in the rebase.
115 115
116 116 By default, rebase recreates the changesets in the source branch
117 117 as descendants of dest and then destroys the originals. Use
118 118 ``--keep`` to preserve the original source changesets. Some
119 119 changesets in the source branch (e.g. merges from the destination
120 120 branch) may be dropped if they no longer contribute any change.
121 121
122 122 One result of the rules for selecting the destination changeset
123 123 and source branch is that, unlike ``merge``, rebase will do
124 124 nothing if you are at the branch tip of a named branch
125 125 with two heads. You need to explicitly specify source and/or
126 126 destination (or ``update`` to the other head, if it's the head of
127 127 the intended source branch).
128 128
129 129 If a rebase is interrupted to manually resolve a merge, it can be
130 130 continued with --continue/-c or aborted with --abort/-a.
131 131
132 132 .. container:: verbose
133 133
134 134 Examples:
135 135
136 136 - move "local changes" (current commit back to branching point)
137 137 to the current branch tip after a pull::
138 138
139 139 hg rebase
140 140
141 141 - move a single changeset to the stable branch::
142 142
143 143 hg rebase -r 5f493448 -d stable
144 144
145 145 - splice a commit and all its descendants onto another part of history::
146 146
147 147 hg rebase --source c0c3 --dest 4cf9
148 148
149 149 - rebase everything on a branch marked by a bookmark onto the
150 150 default branch::
151 151
152 152 hg rebase --base myfeature --dest default
153 153
154 154 - collapse a sequence of changes into a single commit::
155 155
156 156 hg rebase --collapse -r 1520:1525 -d .
157 157
158 158 - move a named branch while preserving its name::
159 159
160 160 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
161 161
162 162 Returns 0 on success, 1 if nothing to rebase or there are
163 163 unresolved conflicts.
164 164
165 165 """
166 166 originalwd = target = None
167 167 activebookmark = None
168 168 external = nullrev
169 169 state = {}
170 170 skipped = set()
171 171 targetancestors = set()
172 172
173 173
174 174 lock = wlock = None
175 175 try:
176 176 wlock = repo.wlock()
177 177 lock = repo.lock()
178 178
179 179 # Validate input and define rebasing points
180 180 destf = opts.get('dest', None)
181 181 srcf = opts.get('source', None)
182 182 basef = opts.get('base', None)
183 183 revf = opts.get('rev', [])
184 184 contf = opts.get('continue')
185 185 abortf = opts.get('abort')
186 186 collapsef = opts.get('collapse', False)
187 187 collapsemsg = cmdutil.logmessage(ui, opts)
188 188 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
189 189 extrafns = [_savegraft]
190 190 if e:
191 191 extrafns = [e]
192 192 keepf = opts.get('keep', False)
193 193 keepbranchesf = opts.get('keepbranches', False)
194 194 # keepopen is not meant for use on the command line, but by
195 195 # other extensions
196 196 keepopen = opts.get('keepopen', False)
197 197
198 198 if opts.get('interactive'):
199 199 msg = _("interactive history editing is supported by the "
200 200 "'histedit' extension (see 'hg help histedit')")
201 201 raise util.Abort(msg)
202 202
203 203 if collapsemsg and not collapsef:
204 204 raise util.Abort(
205 205 _('message can only be specified with collapse'))
206 206
207 207 if contf or abortf:
208 208 if contf and abortf:
209 209 raise util.Abort(_('cannot use both abort and continue'))
210 210 if collapsef:
211 211 raise util.Abort(
212 212 _('cannot use collapse with continue or abort'))
213 213 if srcf or basef or destf:
214 214 raise util.Abort(
215 215 _('abort and continue do not allow specifying revisions'))
216 216 if opts.get('tool', False):
217 217 ui.warn(_('tool option will be ignored\n'))
218 218
219 219 try:
220 220 (originalwd, target, state, skipped, collapsef, keepf,
221 221 keepbranchesf, external, activebookmark) = restorestatus(repo)
222 222 except error.RepoLookupError:
223 223 if abortf:
224 224 clearstatus(repo)
225 225 repo.ui.warn(_('rebase aborted (no revision is removed,'
226 226 ' only broken state is cleared)\n'))
227 227 return 0
228 228 else:
229 229 msg = _('cannot continue inconsistent rebase')
230 230 hint = _('use "hg rebase --abort" to clear broken state')
231 231 raise util.Abort(msg, hint=hint)
232 232 if abortf:
233 233 return abort(repo, originalwd, target, state)
234 234 else:
235 235 if srcf and basef:
236 236 raise util.Abort(_('cannot specify both a '
237 237 'source and a base'))
238 238 if revf and basef:
239 239 raise util.Abort(_('cannot specify both a '
240 240 'revision and a base'))
241 241 if revf and srcf:
242 242 raise util.Abort(_('cannot specify both a '
243 243 'revision and a source'))
244 244
245 245 cmdutil.checkunfinished(repo)
246 246 cmdutil.bailifchanged(repo)
247 247
248 248 if not destf:
249 249 # Destination defaults to the latest revision in the
250 250 # current branch
251 251 branch = repo[None].branch()
252 252 dest = repo[branch]
253 253 else:
254 254 dest = scmutil.revsingle(repo, destf)
255 255
256 256 if revf:
257 257 rebaseset = scmutil.revrange(repo, revf)
258 258 if not rebaseset:
259 259 ui.status(_('empty "rev" revision set - '
260 260 'nothing to rebase\n'))
261 261 return 1
262 262 elif srcf:
263 263 src = scmutil.revrange(repo, [srcf])
264 264 if not src:
265 265 ui.status(_('empty "source" revision set - '
266 266 'nothing to rebase\n'))
267 267 return 1
268 268 rebaseset = repo.revs('(%ld)::', src)
269 269 assert rebaseset
270 270 else:
271 271 base = scmutil.revrange(repo, [basef or '.'])
272 272 if not base:
273 273 ui.status(_('empty "base" revision set - '
274 274 "can't compute rebase set\n"))
275 275 return 1
276 276 commonanc = repo.revs('ancestor(%ld, %d)', base, dest).first()
277 277 if commonanc is not None:
278 278 rebaseset = repo.revs('(%d::(%ld) - %d)::',
279 279 commonanc, base, commonanc)
280 280 else:
281 281 rebaseset = []
282 282
283 283 if not rebaseset:
284 284 # transform to list because smartsets are not comparable to
285 285 # lists. This should be improved to honor lazyness of
286 286 # smartset.
287 287 if list(base) == [dest.rev()]:
288 288 if basef:
289 289 ui.status(_('nothing to rebase - %s is both "base"'
290 290 ' and destination\n') % dest)
291 291 else:
292 292 ui.status(_('nothing to rebase - working directory '
293 293 'parent is also destination\n'))
294 294 elif not repo.revs('%ld - ::%d', base, dest):
295 295 if basef:
296 296 ui.status(_('nothing to rebase - "base" %s is '
297 297 'already an ancestor of destination '
298 298 '%s\n') %
299 299 ('+'.join(str(repo[r]) for r in base),
300 300 dest))
301 301 else:
302 302 ui.status(_('nothing to rebase - working '
303 303 'directory parent is already an '
304 304 'ancestor of destination %s\n') % dest)
305 305 else: # can it happen?
306 306 ui.status(_('nothing to rebase from %s to %s\n') %
307 307 ('+'.join(str(repo[r]) for r in base), dest))
308 308 return 1
309 309
310 310 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
311 311 if (not (keepf or allowunstable)
312 312 and repo.revs('first(children(%ld) - %ld)',
313 313 rebaseset, rebaseset)):
314 314 raise util.Abort(
315 315 _("can't remove original changesets with"
316 316 " unrebased descendants"),
317 317 hint=_('use --keep to keep original changesets'))
318 318
319 319 result = buildstate(repo, dest, rebaseset, collapsef)
320 320 if not result:
321 321 # Empty state built, nothing to rebase
322 322 ui.status(_('nothing to rebase\n'))
323 323 return 1
324 324
325 325 root = min(rebaseset)
326 326 if not keepf and not repo[root].mutable():
327 327 raise util.Abort(_("can't rebase immutable changeset %s")
328 328 % repo[root],
329 329 hint=_('see hg help phases for details'))
330 330
331 331 originalwd, target, state = result
332 332 if collapsef:
333 333 targetancestors = repo.changelog.ancestors([target],
334 334 inclusive=True)
335 335 external = externalparent(repo, state, targetancestors)
336 336
337 337 if dest.closesbranch() and not keepbranchesf:
338 338 ui.status(_('reopening closed branch head %s\n') % dest)
339 339
340 340 if keepbranchesf:
341 341 # insert _savebranch at the start of extrafns so if
342 342 # there's a user-provided extrafn it can clobber branch if
343 343 # desired
344 344 extrafns.insert(0, _savebranch)
345 345 if collapsef:
346 346 branches = set()
347 347 for rev in state:
348 348 branches.add(repo[rev].branch())
349 349 if len(branches) > 1:
350 350 raise util.Abort(_('cannot collapse multiple named '
351 351 'branches'))
352 352
353 353 # Rebase
354 354 if not targetancestors:
355 355 targetancestors = repo.changelog.ancestors([target], inclusive=True)
356 356
357 357 # Keep track of the current bookmarks in order to reset them later
358 358 currentbookmarks = repo._bookmarks.copy()
359 359 activebookmark = activebookmark or repo._bookmarkcurrent
360 360 if activebookmark:
361 361 bookmarks.unsetcurrent(repo)
362 362
363 363 extrafn = _makeextrafn(extrafns)
364 364
365 365 sortedstate = sorted(state)
366 366 total = len(sortedstate)
367 367 pos = 0
368 368 for rev in sortedstate:
369 369 pos += 1
370 370 if state[rev] == -1:
371 371 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
372 372 _('changesets'), total)
373 373 p1, p2 = defineparents(repo, rev, target, state,
374 374 targetancestors)
375 375 storestatus(repo, originalwd, target, state, collapsef, keepf,
376 376 keepbranchesf, external, activebookmark)
377 377 if len(repo.parents()) == 2:
378 378 repo.ui.debug('resuming interrupted rebase\n')
379 379 else:
380 380 try:
381 381 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
382 382 'rebase')
383 383 stats = rebasenode(repo, rev, p1, state, collapsef,
384 384 target)
385 385 if stats and stats[3] > 0:
386 386 raise error.InterventionRequired(
387 387 _('unresolved conflicts (see hg '
388 388 'resolve, then hg rebase --continue)'))
389 389 finally:
390 390 ui.setconfig('ui', 'forcemerge', '', 'rebase')
391 391 if not collapsef:
392 392 merging = repo[p2].rev() != nullrev
393 393 editform = cmdutil.mergeeditform(merging, 'rebase')
394 394 editor = cmdutil.getcommiteditor(editform=editform, **opts)
395 395 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
396 396 editor=editor)
397 397 else:
398 398 # Skip commit if we are collapsing
399 399 repo.dirstate.beginparentchange()
400 400 repo.setparents(repo[p1].node())
401 401 repo.dirstate.endparentchange()
402 402 newrev = None
403 403 # Update the state
404 404 if newrev is not None:
405 405 state[rev] = repo[newrev].rev()
406 406 else:
407 407 if not collapsef:
408 408 ui.note(_('no changes, revision %d skipped\n') % rev)
409 409 ui.debug('next revision set to %s\n' % p1)
410 410 skipped.add(rev)
411 411 state[rev] = p1
412 412
413 413 ui.progress(_('rebasing'), None)
414 414 ui.note(_('rebase merging completed\n'))
415 415
416 416 if collapsef and not keepopen:
417 417 p1, p2 = defineparents(repo, min(state), target,
418 418 state, targetancestors)
419 419 editopt = opts.get('edit')
420 420 editform = 'rebase.collapse'
421 421 if collapsemsg:
422 422 commitmsg = collapsemsg
423 423 else:
424 424 commitmsg = 'Collapsed revision'
425 425 for rebased in state:
426 426 if rebased not in skipped and state[rebased] > nullmerge:
427 427 commitmsg += '\n* %s' % repo[rebased].description()
428 428 editopt = True
429 429 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
430 430 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
431 431 extrafn=extrafn, editor=editor)
432 432 for oldrev in state.iterkeys():
433 433 if state[oldrev] > nullmerge:
434 434 state[oldrev] = newrev
435 435
436 436 if 'qtip' in repo.tags():
437 437 updatemq(repo, state, skipped, **opts)
438 438
439 439 if currentbookmarks:
440 440 # Nodeids are needed to reset bookmarks
441 441 nstate = {}
442 442 for k, v in state.iteritems():
443 443 if v > nullmerge:
444 444 nstate[repo[k].node()] = repo[v].node()
445 445 # XXX this is the same as dest.node() for the non-continue path --
446 446 # this should probably be cleaned up
447 447 targetnode = repo[target].node()
448 448
449 449 # restore original working directory
450 450 # (we do this before stripping)
451 451 newwd = state.get(originalwd, originalwd)
452 if newwd < 0:
453 # original directory is a parent of rebase set root or ignored
454 newwd = originalwd
452 455 if newwd not in [c.rev() for c in repo[None].parents()]:
453 456 ui.note(_("update back to initial working directory parent\n"))
454 457 hg.updaterepo(repo, newwd, False)
455 458
456 459 if not keepf:
457 460 collapsedas = None
458 461 if collapsef:
459 462 collapsedas = newrev
460 463 clearrebased(ui, repo, state, skipped, collapsedas)
461 464
462 465 if currentbookmarks:
463 466 updatebookmarks(repo, targetnode, nstate, currentbookmarks)
464 467 if activebookmark not in repo._bookmarks:
465 468 # active bookmark was divergent one and has been deleted
466 469 activebookmark = None
467 470
468 471 clearstatus(repo)
469 472 ui.note(_("rebase completed\n"))
470 473 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
471 474 if skipped:
472 475 ui.note(_("%d revisions have been skipped\n") % len(skipped))
473 476
474 477 if (activebookmark and
475 478 repo['.'].node() == repo._bookmarks[activebookmark]):
476 479 bookmarks.setcurrent(repo, activebookmark)
477 480
478 481 finally:
479 482 release(lock, wlock)
480 483
481 484 def externalparent(repo, state, targetancestors):
482 485 """Return the revision that should be used as the second parent
483 486 when the revisions in state is collapsed on top of targetancestors.
484 487 Abort if there is more than one parent.
485 488 """
486 489 parents = set()
487 490 source = min(state)
488 491 for rev in state:
489 492 if rev == source:
490 493 continue
491 494 for p in repo[rev].parents():
492 495 if (p.rev() not in state
493 496 and p.rev() not in targetancestors):
494 497 parents.add(p.rev())
495 498 if not parents:
496 499 return nullrev
497 500 if len(parents) == 1:
498 501 return parents.pop()
499 502 raise util.Abort(_('unable to collapse on top of %s, there is more '
500 503 'than one external parent: %s') %
501 504 (max(targetancestors),
502 505 ', '.join(str(p) for p in sorted(parents))))
503 506
504 507 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
505 508 'Commit the changes and store useful information in extra'
506 509 try:
507 510 repo.dirstate.beginparentchange()
508 511 repo.setparents(repo[p1].node(), repo[p2].node())
509 512 repo.dirstate.endparentchange()
510 513 ctx = repo[rev]
511 514 if commitmsg is None:
512 515 commitmsg = ctx.description()
513 516 extra = {'rebase_source': ctx.hex()}
514 517 if extrafn:
515 518 extrafn(ctx, extra)
516 519
517 520 backup = repo.ui.backupconfig('phases', 'new-commit')
518 521 try:
519 522 targetphase = max(ctx.phase(), phases.draft)
520 523 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
521 524 # Commit might fail if unresolved files exist
522 525 newrev = repo.commit(text=commitmsg, user=ctx.user(),
523 526 date=ctx.date(), extra=extra, editor=editor)
524 527 finally:
525 528 repo.ui.restoreconfig(backup)
526 529
527 530 repo.dirstate.setbranch(repo[newrev].branch())
528 531 return newrev
529 532 except util.Abort:
530 533 # Invalidate the previous setparents
531 534 repo.dirstate.invalidate()
532 535 raise
533 536
534 537 def rebasenode(repo, rev, p1, state, collapse, target):
535 538 'Rebase a single revision'
536 539 # Merge phase
537 540 # Update to target and merge it with local
538 541 if repo['.'].rev() != repo[p1].rev():
539 542 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
540 543 merge.update(repo, p1, False, True, False)
541 544 else:
542 545 repo.ui.debug(" already in target\n")
543 546 repo.dirstate.write()
544 547 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
545 548 if repo[rev].rev() == repo[min(state)].rev():
546 549 # Case (1) initial changeset of a non-detaching rebase.
547 550 # Let the merge mechanism find the base itself.
548 551 base = None
549 552 elif not repo[rev].p2():
550 553 # Case (2) detaching the node with a single parent, use this parent
551 554 base = repo[rev].p1().node()
552 555 else:
553 556 # In case of merge, we need to pick the right parent as merge base.
554 557 #
555 558 # Imagine we have:
556 559 # - M: currently rebase revision in this step
557 560 # - A: one parent of M
558 561 # - B: second parent of M
559 562 # - D: destination of this merge step (p1 var)
560 563 #
561 564 # If we are rebasing on D, D is the successors of A or B. The right
562 565 # merge base is the one D succeed to. We pretend it is B for the rest
563 566 # of this comment
564 567 #
565 568 # If we pick B as the base, the merge involves:
566 569 # - changes from B to M (actual changeset payload)
567 570 # - changes from B to D (induced by rebase) as D is a rebased
568 571 # version of B)
569 572 # Which exactly represent the rebase operation.
570 573 #
571 574 # If we pick the A as the base, the merge involves
572 575 # - changes from A to M (actual changeset payload)
573 576 # - changes from A to D (with include changes between unrelated A and B
574 577 # plus changes induced by rebase)
575 578 # Which does not represent anything sensible and creates a lot of
576 579 # conflicts.
577 580 for p in repo[rev].parents():
578 581 if state.get(p.rev()) == repo[p1].rev():
579 582 base = p.node()
580 583 break
581 584 else: # fallback when base not found
582 585 base = None
583 586
584 587 # Raise because this function is called wrong (see issue 4106)
585 588 raise AssertionError('no base found to rebase on '
586 589 '(rebasenode called wrong)')
587 590 if base is not None:
588 591 repo.ui.debug(" detach base %d:%s\n" % (repo[base].rev(), repo[base]))
589 592 # When collapsing in-place, the parent is the common ancestor, we
590 593 # have to allow merging with it.
591 594 stats = merge.update(repo, rev, True, True, False, base, collapse,
592 595 labels=['dest', 'source'])
593 596 if collapse:
594 597 copies.duplicatecopies(repo, rev, target)
595 598 else:
596 599 # If we're not using --collapse, we need to
597 600 # duplicate copies between the revision we're
598 601 # rebasing and its first parent, but *not*
599 602 # duplicate any copies that have already been
600 603 # performed in the destination.
601 604 p1rev = repo[rev].p1().rev()
602 605 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
603 606 return stats
604 607
605 608 def nearestrebased(repo, rev, state):
606 609 """return the nearest ancestors of rev in the rebase result"""
607 610 rebased = [r for r in state if state[r] > nullmerge]
608 611 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
609 612 if candidates:
610 613 return state[candidates.first()]
611 614 else:
612 615 return None
613 616
614 617 def defineparents(repo, rev, target, state, targetancestors):
615 618 'Return the new parent relationship of the revision that will be rebased'
616 619 parents = repo[rev].parents()
617 620 p1 = p2 = nullrev
618 621
619 622 p1n = parents[0].rev()
620 623 if p1n in targetancestors:
621 624 p1 = target
622 625 elif p1n in state:
623 626 if state[p1n] == nullmerge:
624 627 p1 = target
625 628 elif state[p1n] == revignored:
626 629 p1 = nearestrebased(repo, p1n, state)
627 630 if p1 is None:
628 631 p1 = target
629 632 else:
630 633 p1 = state[p1n]
631 634 else: # p1n external
632 635 p1 = target
633 636 p2 = p1n
634 637
635 638 if len(parents) == 2 and parents[1].rev() not in targetancestors:
636 639 p2n = parents[1].rev()
637 640 # interesting second parent
638 641 if p2n in state:
639 642 if p1 == target: # p1n in targetancestors or external
640 643 p1 = state[p2n]
641 644 elif state[p2n] == revignored:
642 645 p2 = nearestrebased(repo, p2n, state)
643 646 if p2 is None:
644 647 # no ancestors rebased yet, detach
645 648 p2 = target
646 649 else:
647 650 p2 = state[p2n]
648 651 else: # p2n external
649 652 if p2 != nullrev: # p1n external too => rev is a merged revision
650 653 raise util.Abort(_('cannot use revision %d as base, result '
651 654 'would have 3 parents') % rev)
652 655 p2 = p2n
653 656 repo.ui.debug(" future parents are %d and %d\n" %
654 657 (repo[p1].rev(), repo[p2].rev()))
655 658 return p1, p2
656 659
657 660 def isagitpatch(repo, patchname):
658 661 'Return true if the given patch is in git format'
659 662 mqpatch = os.path.join(repo.mq.path, patchname)
660 663 for line in patch.linereader(file(mqpatch, 'rb')):
661 664 if line.startswith('diff --git'):
662 665 return True
663 666 return False
664 667
665 668 def updatemq(repo, state, skipped, **opts):
666 669 'Update rebased mq patches - finalize and then import them'
667 670 mqrebase = {}
668 671 mq = repo.mq
669 672 original_series = mq.fullseries[:]
670 673 skippedpatches = set()
671 674
672 675 for p in mq.applied:
673 676 rev = repo[p.node].rev()
674 677 if rev in state:
675 678 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
676 679 (rev, p.name))
677 680 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
678 681 else:
679 682 # Applied but not rebased, not sure this should happen
680 683 skippedpatches.add(p.name)
681 684
682 685 if mqrebase:
683 686 mq.finish(repo, mqrebase.keys())
684 687
685 688 # We must start import from the newest revision
686 689 for rev in sorted(mqrebase, reverse=True):
687 690 if rev not in skipped:
688 691 name, isgit = mqrebase[rev]
689 692 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
690 693 mq.qimport(repo, (), patchname=name, git=isgit,
691 694 rev=[str(state[rev])])
692 695 else:
693 696 # Rebased and skipped
694 697 skippedpatches.add(mqrebase[rev][0])
695 698
696 699 # Patches were either applied and rebased and imported in
697 700 # order, applied and removed or unapplied. Discard the removed
698 701 # ones while preserving the original series order and guards.
699 702 newseries = [s for s in original_series
700 703 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
701 704 mq.fullseries[:] = newseries
702 705 mq.seriesdirty = True
703 706 mq.savedirty()
704 707
705 708 def updatebookmarks(repo, targetnode, nstate, originalbookmarks):
706 709 'Move bookmarks to their correct changesets, and delete divergent ones'
707 710 marks = repo._bookmarks
708 711 for k, v in originalbookmarks.iteritems():
709 712 if v in nstate:
710 713 # update the bookmarks for revs that have moved
711 714 marks[k] = nstate[v]
712 715 bookmarks.deletedivergent(repo, [targetnode], k)
713 716
714 717 marks.write()
715 718
716 719 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
717 720 external, activebookmark):
718 721 'Store the current status to allow recovery'
719 722 f = repo.opener("rebasestate", "w")
720 723 f.write(repo[originalwd].hex() + '\n')
721 724 f.write(repo[target].hex() + '\n')
722 725 f.write(repo[external].hex() + '\n')
723 726 f.write('%d\n' % int(collapse))
724 727 f.write('%d\n' % int(keep))
725 728 f.write('%d\n' % int(keepbranches))
726 729 f.write('%s\n' % (activebookmark or ''))
727 730 for d, v in state.iteritems():
728 731 oldrev = repo[d].hex()
729 732 if v > nullmerge:
730 733 newrev = repo[v].hex()
731 734 else:
732 735 newrev = v
733 736 f.write("%s:%s\n" % (oldrev, newrev))
734 737 f.close()
735 738 repo.ui.debug('rebase status stored\n')
736 739
737 740 def clearstatus(repo):
738 741 'Remove the status files'
739 742 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
740 743
741 744 def restorestatus(repo):
742 745 'Restore a previously stored status'
743 746 try:
744 747 keepbranches = None
745 748 target = None
746 749 collapse = False
747 750 external = nullrev
748 751 activebookmark = None
749 752 state = {}
750 753 f = repo.opener("rebasestate")
751 754 for i, l in enumerate(f.read().splitlines()):
752 755 if i == 0:
753 756 originalwd = repo[l].rev()
754 757 elif i == 1:
755 758 target = repo[l].rev()
756 759 elif i == 2:
757 760 external = repo[l].rev()
758 761 elif i == 3:
759 762 collapse = bool(int(l))
760 763 elif i == 4:
761 764 keep = bool(int(l))
762 765 elif i == 5:
763 766 keepbranches = bool(int(l))
764 767 elif i == 6 and not (len(l) == 81 and ':' in l):
765 768 # line 6 is a recent addition, so for backwards compatibility
766 769 # check that the line doesn't look like the oldrev:newrev lines
767 770 activebookmark = l
768 771 else:
769 772 oldrev, newrev = l.split(':')
770 773 if newrev in (str(nullmerge), str(revignored)):
771 774 state[repo[oldrev].rev()] = int(newrev)
772 775 else:
773 776 state[repo[oldrev].rev()] = repo[newrev].rev()
774 777
775 778 if keepbranches is None:
776 779 raise util.Abort(_('.hg/rebasestate is incomplete'))
777 780
778 781 skipped = set()
779 782 # recompute the set of skipped revs
780 783 if not collapse:
781 784 seen = set([target])
782 785 for old, new in sorted(state.items()):
783 786 if new != nullrev and new in seen:
784 787 skipped.add(old)
785 788 seen.add(new)
786 789 repo.ui.debug('computed skipped revs: %s\n' %
787 790 (' '.join(str(r) for r in sorted(skipped)) or None))
788 791 repo.ui.debug('rebase status resumed\n')
789 792 return (originalwd, target, state, skipped,
790 793 collapse, keep, keepbranches, external, activebookmark)
791 794 except IOError, err:
792 795 if err.errno != errno.ENOENT:
793 796 raise
794 797 raise util.Abort(_('no rebase in progress'))
795 798
796 799 def inrebase(repo, originalwd, state):
797 800 '''check whether the working dir is in an interrupted rebase'''
798 801 parents = [p.rev() for p in repo.parents()]
799 802 if originalwd in parents:
800 803 return True
801 804
802 805 for newrev in state.itervalues():
803 806 if newrev in parents:
804 807 return True
805 808
806 809 return False
807 810
808 811 def abort(repo, originalwd, target, state):
809 812 'Restore the repository to its original state'
810 813 dstates = [s for s in state.values() if s > nullrev]
811 814 immutable = [d for d in dstates if not repo[d].mutable()]
812 815 cleanup = True
813 816 if immutable:
814 817 repo.ui.warn(_("warning: can't clean up immutable changesets %s\n")
815 818 % ', '.join(str(repo[r]) for r in immutable),
816 819 hint=_('see hg help phases for details'))
817 820 cleanup = False
818 821
819 822 descendants = set()
820 823 if dstates:
821 824 descendants = set(repo.changelog.descendants(dstates))
822 825 if descendants - set(dstates):
823 826 repo.ui.warn(_("warning: new changesets detected on target branch, "
824 827 "can't strip\n"))
825 828 cleanup = False
826 829
827 830 if cleanup:
828 831 # Update away from the rebase if necessary
829 832 if inrebase(repo, originalwd, state):
830 833 merge.update(repo, repo[originalwd].rev(), False, True, False)
831 834
832 835 # Strip from the first rebased revision
833 836 rebased = filter(lambda x: x > -1 and x != target, state.values())
834 837 if rebased:
835 838 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
836 839 # no backup of rebased cset versions needed
837 840 repair.strip(repo.ui, repo, strippoints)
838 841
839 842 clearstatus(repo)
840 843 repo.ui.warn(_('rebase aborted\n'))
841 844 return 0
842 845
843 846 def buildstate(repo, dest, rebaseset, collapse):
844 847 '''Define which revisions are going to be rebased and where
845 848
846 849 repo: repo
847 850 dest: context
848 851 rebaseset: set of rev
849 852 '''
850 853
851 854 # This check isn't strictly necessary, since mq detects commits over an
852 855 # applied patch. But it prevents messing up the working directory when
853 856 # a partially completed rebase is blocked by mq.
854 857 if 'qtip' in repo.tags() and (dest.node() in
855 858 [s.node for s in repo.mq.applied]):
856 859 raise util.Abort(_('cannot rebase onto an applied mq patch'))
857 860
858 861 roots = list(repo.set('roots(%ld)', rebaseset))
859 862 if not roots:
860 863 raise util.Abort(_('no matching revisions'))
861 864 roots.sort()
862 865 state = {}
863 866 detachset = set()
864 867 for root in roots:
865 868 commonbase = root.ancestor(dest)
866 869 if commonbase == root:
867 870 raise util.Abort(_('source is ancestor of destination'))
868 871 if commonbase == dest:
869 872 samebranch = root.branch() == dest.branch()
870 873 if not collapse and samebranch and root in dest.children():
871 874 repo.ui.debug('source is a child of destination\n')
872 875 return None
873 876
874 877 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, root))
875 878 state.update(dict.fromkeys(rebaseset, nullrev))
876 879 # Rebase tries to turn <dest> into a parent of <root> while
877 880 # preserving the number of parents of rebased changesets:
878 881 #
879 882 # - A changeset with a single parent will always be rebased as a
880 883 # changeset with a single parent.
881 884 #
882 885 # - A merge will be rebased as merge unless its parents are both
883 886 # ancestors of <dest> or are themselves in the rebased set and
884 887 # pruned while rebased.
885 888 #
886 889 # If one parent of <root> is an ancestor of <dest>, the rebased
887 890 # version of this parent will be <dest>. This is always true with
888 891 # --base option.
889 892 #
890 893 # Otherwise, we need to *replace* the original parents with
891 894 # <dest>. This "detaches" the rebased set from its former location
892 895 # and rebases it onto <dest>. Changes introduced by ancestors of
893 896 # <root> not common with <dest> (the detachset, marked as
894 897 # nullmerge) are "removed" from the rebased changesets.
895 898 #
896 899 # - If <root> has a single parent, set it to <dest>.
897 900 #
898 901 # - If <root> is a merge, we cannot decide which parent to
899 902 # replace, the rebase operation is not clearly defined.
900 903 #
901 904 # The table below sums up this behavior:
902 905 #
903 906 # +------------------+----------------------+-------------------------+
904 907 # | | one parent | merge |
905 908 # +------------------+----------------------+-------------------------+
906 909 # | parent in | new parent is <dest> | parents in ::<dest> are |
907 910 # | ::<dest> | | remapped to <dest> |
908 911 # +------------------+----------------------+-------------------------+
909 912 # | unrelated source | new parent is <dest> | ambiguous, abort |
910 913 # +------------------+----------------------+-------------------------+
911 914 #
912 915 # The actual abort is handled by `defineparents`
913 916 if len(root.parents()) <= 1:
914 917 # ancestors of <root> not ancestors of <dest>
915 918 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
916 919 [root.rev()]))
917 920 for r in detachset:
918 921 if r not in state:
919 922 state[r] = nullmerge
920 923 if len(roots) > 1:
921 924 # If we have multiple roots, we may have "hole" in the rebase set.
922 925 # Rebase roots that descend from those "hole" should not be detached as
923 926 # other root are. We use the special `revignored` to inform rebase that
924 927 # the revision should be ignored but that `defineparents` should search
925 928 # a rebase destination that make sense regarding rebased topology.
926 929 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
927 930 for ignored in set(rebasedomain) - set(rebaseset):
928 931 state[ignored] = revignored
929 932 return repo['.'].rev(), dest.rev(), state
930 933
931 934 def clearrebased(ui, repo, state, skipped, collapsedas=None):
932 935 """dispose of rebased revision at the end of the rebase
933 936
934 937 If `collapsedas` is not None, the rebase was a collapse whose result if the
935 938 `collapsedas` node."""
936 939 if obsolete.isenabled(repo, obsolete.createmarkersopt):
937 940 markers = []
938 941 for rev, newrev in sorted(state.items()):
939 942 if newrev >= 0:
940 943 if rev in skipped:
941 944 succs = ()
942 945 elif collapsedas is not None:
943 946 succs = (repo[collapsedas],)
944 947 else:
945 948 succs = (repo[newrev],)
946 949 markers.append((repo[rev], succs))
947 950 if markers:
948 951 obsolete.createmarkers(repo, markers)
949 952 else:
950 953 rebased = [rev for rev in state if state[rev] > nullmerge]
951 954 if rebased:
952 955 stripped = []
953 956 for root in repo.set('roots(%ld)', rebased):
954 957 if set(repo.changelog.descendants([root.rev()])) - set(state):
955 958 ui.warn(_("warning: new changesets detected "
956 959 "on source branch, not stripping\n"))
957 960 else:
958 961 stripped.append(root.node())
959 962 if stripped:
960 963 # backup the old csets by default
961 964 repair.strip(ui, repo, stripped, "all")
962 965
963 966
964 967 def pullrebase(orig, ui, repo, *args, **opts):
965 968 'Call rebase after pull if the latter has been invoked with --rebase'
966 969 if opts.get('rebase'):
967 970 if opts.get('update'):
968 971 del opts['update']
969 972 ui.debug('--update and --rebase are not compatible, ignoring '
970 973 'the update flag\n')
971 974
972 975 movemarkfrom = repo['.'].node()
973 976 revsprepull = len(repo)
974 977 origpostincoming = commands.postincoming
975 978 def _dummy(*args, **kwargs):
976 979 pass
977 980 commands.postincoming = _dummy
978 981 try:
979 982 orig(ui, repo, *args, **opts)
980 983 finally:
981 984 commands.postincoming = origpostincoming
982 985 revspostpull = len(repo)
983 986 if revspostpull > revsprepull:
984 987 # --rev option from pull conflict with rebase own --rev
985 988 # dropping it
986 989 if 'rev' in opts:
987 990 del opts['rev']
988 991 rebase(ui, repo, **opts)
989 992 branch = repo[None].branch()
990 993 dest = repo[branch].rev()
991 994 if dest != repo['.'].rev():
992 995 # there was nothing to rebase we force an update
993 996 hg.update(repo, dest)
994 997 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
995 998 ui.status(_("updating bookmark %s\n")
996 999 % repo._bookmarkcurrent)
997 1000 else:
998 1001 if opts.get('tool'):
999 1002 raise util.Abort(_('--tool can only be used with --rebase'))
1000 1003 orig(ui, repo, *args, **opts)
1001 1004
1002 1005 def summaryhook(ui, repo):
1003 1006 if not os.path.exists(repo.join('rebasestate')):
1004 1007 return
1005 1008 try:
1006 1009 state = restorestatus(repo)[2]
1007 1010 except error.RepoLookupError:
1008 1011 # i18n: column positioning for "hg summary"
1009 1012 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1010 1013 ui.write(msg)
1011 1014 return
1012 1015 numrebased = len([i for i in state.itervalues() if i != -1])
1013 1016 # i18n: column positioning for "hg summary"
1014 1017 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1015 1018 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1016 1019 ui.label(_('%d remaining'), 'rebase.remaining') %
1017 1020 (len(state) - numrebased)))
1018 1021
1019 1022 def uisetup(ui):
1020 1023 'Replace pull with a decorator to provide --rebase option'
1021 1024 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1022 1025 entry[1].append(('', 'rebase', None,
1023 1026 _("rebase working directory to branch head")))
1024 1027 entry[1].append(('t', 'tool', '',
1025 1028 _("specify merge tool for rebase")))
1026 1029 cmdutil.summaryhooks.add('rebase', summaryhook)
1027 1030 cmdutil.unfinishedstates.append(
1028 1031 ['rebasestate', False, False, _('rebase in progress'),
1029 1032 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
@@ -1,689 +1,693
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > rebase=
4 4 >
5 5 > [phases]
6 6 > publish=False
7 7 >
8 8 > [alias]
9 9 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
10 10 > EOF
11 11
12 12
13 13 $ hg init a
14 14 $ cd a
15 15 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
16 16 adding changesets
17 17 adding manifests
18 18 adding file changes
19 19 added 8 changesets with 7 changes to 7 files (+2 heads)
20 20 (run 'hg heads' to see heads, 'hg merge' to merge)
21 21 $ hg up tip
22 22 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
23 23 $ cd ..
24 24
25 25
26 26 Rebasing
27 27 D onto H - simple rebase:
28 28 (this also tests that editor is invoked if '--edit' is specified)
29 29
30 30 $ hg clone -q -u . a a1
31 31 $ cd a1
32 32
33 33 $ hg tglog
34 34 @ 7: 'H'
35 35 |
36 36 | o 6: 'G'
37 37 |/|
38 38 o | 5: 'F'
39 39 | |
40 40 | o 4: 'E'
41 41 |/
42 42 | o 3: 'D'
43 43 | |
44 44 | o 2: 'C'
45 45 | |
46 46 | o 1: 'B'
47 47 |/
48 48 o 0: 'A'
49 49
50 50
51 51 $ hg status --rev "3^1" --rev 3
52 52 A D
53 53 $ HGEDITOR=cat hg rebase -s 3 -d 7 --edit
54 54 D
55 55
56 56
57 57 HG: Enter commit message. Lines beginning with 'HG:' are removed.
58 58 HG: Leave message empty to abort commit.
59 59 HG: --
60 60 HG: user: Nicolas Dumazet <nicdumz.commits@gmail.com>
61 61 HG: branch 'default'
62 62 HG: added D
63 63 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob)
64 64
65 65 $ hg tglog
66 66 o 7: 'D'
67 67 |
68 68 @ 6: 'H'
69 69 |
70 70 | o 5: 'G'
71 71 |/|
72 72 o | 4: 'F'
73 73 | |
74 74 | o 3: 'E'
75 75 |/
76 76 | o 2: 'C'
77 77 | |
78 78 | o 1: 'B'
79 79 |/
80 80 o 0: 'A'
81 81
82 82 $ cd ..
83 83
84 84
85 85 D onto F - intermediate point:
86 86 (this also tests that editor is not invoked if '--edit' is not specified)
87 87
88 88 $ hg clone -q -u . a a2
89 89 $ cd a2
90 90
91 91 $ HGEDITOR=cat hg rebase -s 3 -d 5
92 92 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob)
93 93
94 94 $ hg tglog
95 95 o 7: 'D'
96 96 |
97 97 | @ 6: 'H'
98 98 |/
99 99 | o 5: 'G'
100 100 |/|
101 101 o | 4: 'F'
102 102 | |
103 103 | o 3: 'E'
104 104 |/
105 105 | o 2: 'C'
106 106 | |
107 107 | o 1: 'B'
108 108 |/
109 109 o 0: 'A'
110 110
111 111 $ cd ..
112 112
113 113
114 114 E onto H - skip of G:
115 115
116 116 $ hg clone -q -u . a a3
117 117 $ cd a3
118 118
119 119 $ hg rebase -s 4 -d 7
120 120 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob)
121 121
122 122 $ hg tglog
123 123 o 6: 'E'
124 124 |
125 125 @ 5: 'H'
126 126 |
127 127 o 4: 'F'
128 128 |
129 129 | o 3: 'D'
130 130 | |
131 131 | o 2: 'C'
132 132 | |
133 133 | o 1: 'B'
134 134 |/
135 135 o 0: 'A'
136 136
137 137 $ cd ..
138 138
139 139
140 140 F onto E - rebase of a branching point (skip G):
141 141
142 142 $ hg clone -q -u . a a4
143 143 $ cd a4
144 144
145 145 $ hg rebase -s 5 -d 4
146 146 saved backup bundle to $TESTTMP/a4/.hg/strip-backup/*-backup.hg (glob)
147 147
148 148 $ hg tglog
149 149 @ 6: 'H'
150 150 |
151 151 o 5: 'F'
152 152 |
153 153 o 4: 'E'
154 154 |
155 155 | o 3: 'D'
156 156 | |
157 157 | o 2: 'C'
158 158 | |
159 159 | o 1: 'B'
160 160 |/
161 161 o 0: 'A'
162 162
163 163 $ cd ..
164 164
165 165
166 166 G onto H - merged revision having a parent in ancestors of target:
167 167
168 168 $ hg clone -q -u . a a5
169 169 $ cd a5
170 170
171 171 $ hg rebase -s 6 -d 7
172 172 saved backup bundle to $TESTTMP/a5/.hg/strip-backup/*-backup.hg (glob)
173 173
174 174 $ hg tglog
175 175 o 7: 'G'
176 176 |\
177 177 | @ 6: 'H'
178 178 | |
179 179 | o 5: 'F'
180 180 | |
181 181 o | 4: 'E'
182 182 |/
183 183 | o 3: 'D'
184 184 | |
185 185 | o 2: 'C'
186 186 | |
187 187 | o 1: 'B'
188 188 |/
189 189 o 0: 'A'
190 190
191 191 $ cd ..
192 192
193 193
194 194 F onto B - G maintains E as parent:
195 195
196 196 $ hg clone -q -u . a a6
197 197 $ cd a6
198 198
199 199 $ hg rebase -s 5 -d 1
200 200 saved backup bundle to $TESTTMP/a6/.hg/strip-backup/*-backup.hg (glob)
201 201
202 202 $ hg tglog
203 203 @ 7: 'H'
204 204 |
205 205 | o 6: 'G'
206 206 |/|
207 207 o | 5: 'F'
208 208 | |
209 209 | o 4: 'E'
210 210 | |
211 211 | | o 3: 'D'
212 212 | | |
213 213 +---o 2: 'C'
214 214 | |
215 215 o | 1: 'B'
216 216 |/
217 217 o 0: 'A'
218 218
219 219 $ cd ..
220 220
221 221
222 222 These will fail (using --source):
223 223
224 224 G onto F - rebase onto an ancestor:
225 225
226 226 $ hg clone -q -u . a a7
227 227 $ cd a7
228 228
229 229 $ hg rebase -s 6 -d 5
230 230 nothing to rebase
231 231 [1]
232 232
233 233 F onto G - rebase onto a descendant:
234 234
235 235 $ hg rebase -s 5 -d 6
236 236 abort: source is ancestor of destination
237 237 [255]
238 238
239 239 G onto B - merge revision with both parents not in ancestors of target:
240 240
241 241 $ hg rebase -s 6 -d 1
242 242 abort: cannot use revision 6 as base, result would have 3 parents
243 243 [255]
244 244
245 245
246 246 These will abort gracefully (using --base):
247 247
248 248 G onto G - rebase onto same changeset:
249 249
250 250 $ hg rebase -b 6 -d 6
251 251 nothing to rebase - eea13746799a is both "base" and destination
252 252 [1]
253 253
254 254 G onto F - rebase onto an ancestor:
255 255
256 256 $ hg rebase -b 6 -d 5
257 257 nothing to rebase
258 258 [1]
259 259
260 260 F onto G - rebase onto a descendant:
261 261
262 262 $ hg rebase -b 5 -d 6
263 263 nothing to rebase - "base" 24b6387c8c8c is already an ancestor of destination eea13746799a
264 264 [1]
265 265
266 266 C onto A - rebase onto an ancestor:
267 267
268 268 $ hg rebase -d 0 -s 2
269 269 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/5fddd98957c8-backup.hg (glob)
270 270 $ hg tglog
271 271 o 7: 'D'
272 272 |
273 273 o 6: 'C'
274 274 |
275 275 | @ 5: 'H'
276 276 | |
277 277 | | o 4: 'G'
278 278 | |/|
279 279 | o | 3: 'F'
280 280 |/ /
281 281 | o 2: 'E'
282 282 |/
283 283 | o 1: 'B'
284 284 |/
285 285 o 0: 'A'
286 286
287 287
288 288 Check rebasing public changeset
289 289
290 290 $ hg pull --config phases.publish=True -q -r 6 . # update phase of 6
291 291 $ hg rebase -d 0 -b 6
292 292 nothing to rebase
293 293 [1]
294 294 $ hg rebase -d 5 -b 6
295 295 abort: can't rebase immutable changeset e1c4361dd923
296 296 (see hg help phases for details)
297 297 [255]
298 298
299 299 $ hg rebase -d 5 -b 6 --keep
300 300
301 301 Check rebasing mutable changeset
302 302 Source phase greater or equal to destination phase: new changeset get the phase of source:
303 $ hg id -n
304 5
303 305 $ hg rebase -s9 -d0
304 306 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2b23e52411f4-backup.hg (glob)
307 $ hg id -n # check we updated back to parent
308 5
305 309 $ hg log --template "{phase}\n" -r 9
306 310 draft
307 311 $ hg rebase -s9 -d1
308 312 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2cb10d0cfc6c-backup.hg (glob)
309 313 $ hg log --template "{phase}\n" -r 9
310 314 draft
311 315 $ hg phase --force --secret 9
312 316 $ hg rebase -s9 -d0
313 317 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/c5b12b67163a-backup.hg (glob)
314 318 $ hg log --template "{phase}\n" -r 9
315 319 secret
316 320 $ hg rebase -s9 -d1
317 321 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2a0524f868ac-backup.hg (glob)
318 322 $ hg log --template "{phase}\n" -r 9
319 323 secret
320 324 Source phase lower than destination phase: new changeset get the phase of destination:
321 325 $ hg rebase -s8 -d9
322 326 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/6d4f22462821-backup.hg (glob)
323 327 $ hg log --template "{phase}\n" -r 'rev(9)'
324 328 secret
325 329
326 330 $ cd ..
327 331
328 332 Test for revset
329 333
330 334 We need a bit different graph
331 335 All destination are B
332 336
333 337 $ hg init ah
334 338 $ cd ah
335 339 $ hg unbundle "$TESTDIR/bundles/rebase-revset.hg"
336 340 adding changesets
337 341 adding manifests
338 342 adding file changes
339 343 added 9 changesets with 9 changes to 9 files (+2 heads)
340 344 (run 'hg heads' to see heads, 'hg merge' to merge)
341 345 $ hg tglog
342 346 o 8: 'I'
343 347 |
344 348 o 7: 'H'
345 349 |
346 350 o 6: 'G'
347 351 |
348 352 | o 5: 'F'
349 353 | |
350 354 | o 4: 'E'
351 355 |/
352 356 o 3: 'D'
353 357 |
354 358 o 2: 'C'
355 359 |
356 360 | o 1: 'B'
357 361 |/
358 362 o 0: 'A'
359 363
360 364 $ cd ..
361 365
362 366
363 367 Simple case with keep:
364 368
365 369 Source on have two descendant heads but ask for one
366 370
367 371 $ hg clone -q -u . ah ah1
368 372 $ cd ah1
369 373 $ hg rebase -r '2::8' -d 1
370 374 abort: can't remove original changesets with unrebased descendants
371 375 (use --keep to keep original changesets)
372 376 [255]
373 377 $ hg rebase -r '2::8' -d 1 --keep
374 378 $ hg tglog
375 379 o 13: 'I'
376 380 |
377 381 o 12: 'H'
378 382 |
379 383 o 11: 'G'
380 384 |
381 385 o 10: 'D'
382 386 |
383 387 o 9: 'C'
384 388 |
385 389 | o 8: 'I'
386 390 | |
387 391 | o 7: 'H'
388 392 | |
389 393 | o 6: 'G'
390 394 | |
391 395 | | o 5: 'F'
392 396 | | |
393 397 | | o 4: 'E'
394 398 | |/
395 399 | o 3: 'D'
396 400 | |
397 401 | o 2: 'C'
398 402 | |
399 403 o | 1: 'B'
400 404 |/
401 405 o 0: 'A'
402 406
403 407
404 408 $ cd ..
405 409
406 410 Base on have one descendant heads we ask for but common ancestor have two
407 411
408 412 $ hg clone -q -u . ah ah2
409 413 $ cd ah2
410 414 $ hg rebase -r '3::8' -d 1
411 415 abort: can't remove original changesets with unrebased descendants
412 416 (use --keep to keep original changesets)
413 417 [255]
414 418 $ hg rebase -r '3::8' -d 1 --keep
415 419 $ hg tglog
416 420 o 12: 'I'
417 421 |
418 422 o 11: 'H'
419 423 |
420 424 o 10: 'G'
421 425 |
422 426 o 9: 'D'
423 427 |
424 428 | o 8: 'I'
425 429 | |
426 430 | o 7: 'H'
427 431 | |
428 432 | o 6: 'G'
429 433 | |
430 434 | | o 5: 'F'
431 435 | | |
432 436 | | o 4: 'E'
433 437 | |/
434 438 | o 3: 'D'
435 439 | |
436 440 | o 2: 'C'
437 441 | |
438 442 o | 1: 'B'
439 443 |/
440 444 o 0: 'A'
441 445
442 446
443 447 $ cd ..
444 448
445 449 rebase subset
446 450
447 451 $ hg clone -q -u . ah ah3
448 452 $ cd ah3
449 453 $ hg rebase -r '3::7' -d 1
450 454 abort: can't remove original changesets with unrebased descendants
451 455 (use --keep to keep original changesets)
452 456 [255]
453 457 $ hg rebase -r '3::7' -d 1 --keep
454 458 $ hg tglog
455 459 o 11: 'H'
456 460 |
457 461 o 10: 'G'
458 462 |
459 463 o 9: 'D'
460 464 |
461 465 | o 8: 'I'
462 466 | |
463 467 | o 7: 'H'
464 468 | |
465 469 | o 6: 'G'
466 470 | |
467 471 | | o 5: 'F'
468 472 | | |
469 473 | | o 4: 'E'
470 474 | |/
471 475 | o 3: 'D'
472 476 | |
473 477 | o 2: 'C'
474 478 | |
475 479 o | 1: 'B'
476 480 |/
477 481 o 0: 'A'
478 482
479 483
480 484 $ cd ..
481 485
482 486 rebase subset with multiple head
483 487
484 488 $ hg clone -q -u . ah ah4
485 489 $ cd ah4
486 490 $ hg rebase -r '3::(7+5)' -d 1
487 491 abort: can't remove original changesets with unrebased descendants
488 492 (use --keep to keep original changesets)
489 493 [255]
490 494 $ hg rebase -r '3::(7+5)' -d 1 --keep
491 495 $ hg tglog
492 496 o 13: 'H'
493 497 |
494 498 o 12: 'G'
495 499 |
496 500 | o 11: 'F'
497 501 | |
498 502 | o 10: 'E'
499 503 |/
500 504 o 9: 'D'
501 505 |
502 506 | o 8: 'I'
503 507 | |
504 508 | o 7: 'H'
505 509 | |
506 510 | o 6: 'G'
507 511 | |
508 512 | | o 5: 'F'
509 513 | | |
510 514 | | o 4: 'E'
511 515 | |/
512 516 | o 3: 'D'
513 517 | |
514 518 | o 2: 'C'
515 519 | |
516 520 o | 1: 'B'
517 521 |/
518 522 o 0: 'A'
519 523
520 524
521 525 $ cd ..
522 526
523 527 More advanced tests
524 528
525 529 rebase on ancestor with revset
526 530
527 531 $ hg clone -q -u . ah ah5
528 532 $ cd ah5
529 533 $ hg rebase -r '6::' -d 2
530 534 saved backup bundle to $TESTTMP/ah5/.hg/strip-backup/3d8a618087a7-backup.hg (glob)
531 535 $ hg tglog
532 536 o 8: 'I'
533 537 |
534 538 o 7: 'H'
535 539 |
536 540 o 6: 'G'
537 541 |
538 542 | o 5: 'F'
539 543 | |
540 544 | o 4: 'E'
541 545 | |
542 546 | o 3: 'D'
543 547 |/
544 548 o 2: 'C'
545 549 |
546 550 | o 1: 'B'
547 551 |/
548 552 o 0: 'A'
549 553
550 554 $ cd ..
551 555
552 556
553 557 rebase with multiple root.
554 558 We rebase E and G on B
555 559 We would expect heads are I, F if it was supported
556 560
557 561 $ hg clone -q -u . ah ah6
558 562 $ cd ah6
559 563 $ hg rebase -r '(4+6)::' -d 1
560 564 saved backup bundle to $TESTTMP/ah6/.hg/strip-backup/3d8a618087a7-backup.hg (glob)
561 565 $ hg tglog
562 566 o 8: 'I'
563 567 |
564 568 o 7: 'H'
565 569 |
566 570 o 6: 'G'
567 571 |
568 572 | o 5: 'F'
569 573 | |
570 574 | o 4: 'E'
571 575 |/
572 576 | o 3: 'D'
573 577 | |
574 578 | o 2: 'C'
575 579 | |
576 580 o | 1: 'B'
577 581 |/
578 582 o 0: 'A'
579 583
580 584 $ cd ..
581 585
582 586 More complex rebase with multiple roots
583 587 each root have a different common ancestor with the destination and this is a detach
584 588
585 589 (setup)
586 590
587 591 $ hg clone -q -u . a a8
588 592 $ cd a8
589 593 $ echo I > I
590 594 $ hg add I
591 595 $ hg commit -m I
592 596 $ hg up 4
593 597 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
594 598 $ echo I > J
595 599 $ hg add J
596 600 $ hg commit -m J
597 601 created new head
598 602 $ echo I > K
599 603 $ hg add K
600 604 $ hg commit -m K
601 605 $ hg tglog
602 606 @ 10: 'K'
603 607 |
604 608 o 9: 'J'
605 609 |
606 610 | o 8: 'I'
607 611 | |
608 612 | o 7: 'H'
609 613 | |
610 614 +---o 6: 'G'
611 615 | |/
612 616 | o 5: 'F'
613 617 | |
614 618 o | 4: 'E'
615 619 |/
616 620 | o 3: 'D'
617 621 | |
618 622 | o 2: 'C'
619 623 | |
620 624 | o 1: 'B'
621 625 |/
622 626 o 0: 'A'
623 627
624 628 (actual test)
625 629
626 630 $ hg rebase --dest 'desc(G)' --rev 'desc(K) + desc(I)'
627 631 saved backup bundle to $TESTTMP/a8/.hg/strip-backup/23a4ace37988-backup.hg (glob)
628 632 $ hg log --rev 'children(desc(G))'
629 633 changeset: 9:adb617877056
630 634 parent: 6:eea13746799a
631 635 user: test
632 636 date: Thu Jan 01 00:00:00 1970 +0000
633 637 summary: I
634 638
635 639 changeset: 10:882431a34a0e
636 640 tag: tip
637 641 parent: 6:eea13746799a
638 642 user: test
639 643 date: Thu Jan 01 00:00:00 1970 +0000
640 644 summary: K
641 645
642 646 $ hg tglog
643 647 @ 10: 'K'
644 648 |
645 649 | o 9: 'I'
646 650 |/
647 651 | o 8: 'J'
648 652 | |
649 653 | | o 7: 'H'
650 654 | | |
651 655 o---+ 6: 'G'
652 656 |/ /
653 657 | o 5: 'F'
654 658 | |
655 659 o | 4: 'E'
656 660 |/
657 661 | o 3: 'D'
658 662 | |
659 663 | o 2: 'C'
660 664 | |
661 665 | o 1: 'B'
662 666 |/
663 667 o 0: 'A'
664 668
665 669
666 670 Test that rebase is not confused by $CWD disappearing during rebase (issue4121)
667 671
668 672 $ cd ..
669 673 $ hg init cwd-vanish
670 674 $ cd cwd-vanish
671 675 $ touch initial-file
672 676 $ hg add initial-file
673 677 $ hg commit -m 'initial commit'
674 678 $ touch dest-file
675 679 $ hg add dest-file
676 680 $ hg commit -m 'dest commit'
677 681 $ hg up 0
678 682 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
679 683 $ touch other-file
680 684 $ hg add other-file
681 685 $ hg commit -m 'first source commit'
682 686 created new head
683 687 $ mkdir subdir
684 688 $ cd subdir
685 689 $ touch subfile
686 690 $ hg add subfile
687 691 $ hg commit -m 'second source with subdir'
688 692 $ hg rebase -b . -d 1 --traceback
689 693 saved backup bundle to $TESTTMP/cwd-vanish/.hg/strip-backup/779a07b1b7a0-backup.hg (glob)
General Comments 0
You need to be logged in to leave comments. Login now