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