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