##// END OF EJS Templates
rebase: add flag to require destination...
Ryan McElroy -
r31558:13dc00c2 default
parent child Browse files
Show More
@@ -1,1503 +1,1519
1 1 # rebase.py - rebasing feature for mercurial
2 2 #
3 3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''command to move sets of revisions to a different ancestor
9 9
10 10 This extension lets you rebase changesets in an existing Mercurial
11 11 repository.
12 12
13 13 For more information:
14 14 https://mercurial-scm.org/wiki/RebaseExtension
15 15 '''
16 16
17 17 from __future__ import absolute_import
18 18
19 19 import errno
20 20 import os
21 21
22 22 from mercurial.i18n import _
23 23 from mercurial.node import (
24 24 hex,
25 25 nullid,
26 26 nullrev,
27 27 short,
28 28 )
29 29 from mercurial import (
30 30 bookmarks,
31 31 cmdutil,
32 32 commands,
33 33 copies,
34 34 destutil,
35 35 dirstateguard,
36 36 error,
37 37 extensions,
38 38 hg,
39 39 lock,
40 40 merge as mergemod,
41 41 mergeutil,
42 42 obsolete,
43 43 patch,
44 44 phases,
45 45 registrar,
46 46 repair,
47 47 repoview,
48 48 revset,
49 49 scmutil,
50 50 smartset,
51 51 util,
52 52 )
53 53
54 54 release = lock.release
55 55 templateopts = commands.templateopts
56 56
57 57 # The following constants are used throughout the rebase module. The ordering of
58 58 # their values must be maintained.
59 59
60 60 # Indicates that a revision needs to be rebased
61 61 revtodo = -1
62 62 nullmerge = -2
63 63 revignored = -3
64 64 # successor in rebase destination
65 65 revprecursor = -4
66 66 # plain prune (no successor)
67 67 revpruned = -5
68 68 revskipped = (revignored, revprecursor, revpruned)
69 69
70 70 cmdtable = {}
71 71 command = cmdutil.command(cmdtable)
72 72 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
73 73 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
74 74 # be specifying the version(s) of Mercurial they are tested with, or
75 75 # leave the attribute unspecified.
76 76 testedwith = 'ships-with-hg-core'
77 77
78 78 def _nothingtorebase():
79 79 return 1
80 80
81 81 def _savegraft(ctx, extra):
82 82 s = ctx.extra().get('source', None)
83 83 if s is not None:
84 84 extra['source'] = s
85 85 s = ctx.extra().get('intermediate-source', None)
86 86 if s is not None:
87 87 extra['intermediate-source'] = s
88 88
89 89 def _savebranch(ctx, extra):
90 90 extra['branch'] = ctx.branch()
91 91
92 92 def _makeextrafn(copiers):
93 93 """make an extrafn out of the given copy-functions.
94 94
95 95 A copy function takes a context and an extra dict, and mutates the
96 96 extra dict as needed based on the given context.
97 97 """
98 98 def extrafn(ctx, extra):
99 99 for c in copiers:
100 100 c(ctx, extra)
101 101 return extrafn
102 102
103 103 def _destrebase(repo, sourceset, destspace=None):
104 104 """small wrapper around destmerge to pass the right extra args
105 105
106 106 Please wrap destutil.destmerge instead."""
107 107 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
108 108 onheadcheck=False, destspace=destspace)
109 109
110 110 revsetpredicate = registrar.revsetpredicate()
111 111
112 112 @revsetpredicate('_destrebase')
113 113 def _revsetdestrebase(repo, subset, x):
114 114 # ``_rebasedefaultdest()``
115 115
116 116 # default destination for rebase.
117 117 # # XXX: Currently private because I expect the signature to change.
118 118 # # XXX: - bailing out in case of ambiguity vs returning all data.
119 119 # i18n: "_rebasedefaultdest" is a keyword
120 120 sourceset = None
121 121 if x is not None:
122 122 sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
123 123 return subset & smartset.baseset([_destrebase(repo, sourceset)])
124 124
125 125 class rebaseruntime(object):
126 126 """This class is a container for rebase runtime state"""
127 127 def __init__(self, repo, ui, opts=None):
128 128 if opts is None:
129 129 opts = {}
130 130
131 131 self.repo = repo
132 132 self.ui = ui
133 133 self.opts = opts
134 134 self.originalwd = None
135 135 self.external = nullrev
136 136 # Mapping between the old revision id and either what is the new rebased
137 137 # revision or what needs to be done with the old revision. The state
138 138 # dict will be what contains most of the rebase progress state.
139 139 self.state = {}
140 140 self.activebookmark = None
141 141 self.currentbookmarks = None
142 142 self.target = None
143 143 self.skipped = set()
144 144 self.targetancestors = set()
145 145
146 146 self.collapsef = opts.get('collapse', False)
147 147 self.collapsemsg = cmdutil.logmessage(ui, opts)
148 148 self.date = opts.get('date', None)
149 149
150 150 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
151 151 self.extrafns = [_savegraft]
152 152 if e:
153 153 self.extrafns = [e]
154 154
155 155 self.keepf = opts.get('keep', False)
156 156 self.keepbranchesf = opts.get('keepbranches', False)
157 157 # keepopen is not meant for use on the command line, but by
158 158 # other extensions
159 159 self.keepopen = opts.get('keepopen', False)
160 160 self.obsoletenotrebased = {}
161 161
162 162 def storestatus(self, tr=None):
163 163 """Store the current status to allow recovery"""
164 164 if tr:
165 165 tr.addfilegenerator('rebasestate', ('rebasestate',),
166 166 self._writestatus, location='plain')
167 167 else:
168 168 with self.repo.vfs("rebasestate", "w") as f:
169 169 self._writestatus(f)
170 170
171 171 def _writestatus(self, f):
172 172 repo = self.repo
173 173 f.write(repo[self.originalwd].hex() + '\n')
174 174 f.write(repo[self.target].hex() + '\n')
175 175 f.write(repo[self.external].hex() + '\n')
176 176 f.write('%d\n' % int(self.collapsef))
177 177 f.write('%d\n' % int(self.keepf))
178 178 f.write('%d\n' % int(self.keepbranchesf))
179 179 f.write('%s\n' % (self.activebookmark or ''))
180 180 for d, v in self.state.iteritems():
181 181 oldrev = repo[d].hex()
182 182 if v >= 0:
183 183 newrev = repo[v].hex()
184 184 elif v == revtodo:
185 185 # To maintain format compatibility, we have to use nullid.
186 186 # Please do remove this special case when upgrading the format.
187 187 newrev = hex(nullid)
188 188 else:
189 189 newrev = v
190 190 f.write("%s:%s\n" % (oldrev, newrev))
191 191 repo.ui.debug('rebase status stored\n')
192 192
193 193 def restorestatus(self):
194 194 """Restore a previously stored status"""
195 195 repo = self.repo
196 196 keepbranches = None
197 197 target = None
198 198 collapse = False
199 199 external = nullrev
200 200 activebookmark = None
201 201 state = {}
202 202
203 203 try:
204 204 f = repo.vfs("rebasestate")
205 205 for i, l in enumerate(f.read().splitlines()):
206 206 if i == 0:
207 207 originalwd = repo[l].rev()
208 208 elif i == 1:
209 209 target = repo[l].rev()
210 210 elif i == 2:
211 211 external = repo[l].rev()
212 212 elif i == 3:
213 213 collapse = bool(int(l))
214 214 elif i == 4:
215 215 keep = bool(int(l))
216 216 elif i == 5:
217 217 keepbranches = bool(int(l))
218 218 elif i == 6 and not (len(l) == 81 and ':' in l):
219 219 # line 6 is a recent addition, so for backwards
220 220 # compatibility check that the line doesn't look like the
221 221 # oldrev:newrev lines
222 222 activebookmark = l
223 223 else:
224 224 oldrev, newrev = l.split(':')
225 225 if newrev in (str(nullmerge), str(revignored),
226 226 str(revprecursor), str(revpruned)):
227 227 state[repo[oldrev].rev()] = int(newrev)
228 228 elif newrev == nullid:
229 229 state[repo[oldrev].rev()] = revtodo
230 230 # Legacy compat special case
231 231 else:
232 232 state[repo[oldrev].rev()] = repo[newrev].rev()
233 233
234 234 except IOError as err:
235 235 if err.errno != errno.ENOENT:
236 236 raise
237 237 cmdutil.wrongtooltocontinue(repo, _('rebase'))
238 238
239 239 if keepbranches is None:
240 240 raise error.Abort(_('.hg/rebasestate is incomplete'))
241 241
242 242 skipped = set()
243 243 # recompute the set of skipped revs
244 244 if not collapse:
245 245 seen = set([target])
246 246 for old, new in sorted(state.items()):
247 247 if new != revtodo and new in seen:
248 248 skipped.add(old)
249 249 seen.add(new)
250 250 repo.ui.debug('computed skipped revs: %s\n' %
251 251 (' '.join(str(r) for r in sorted(skipped)) or None))
252 252 repo.ui.debug('rebase status resumed\n')
253 253 _setrebasesetvisibility(repo, set(state.keys()) | set([originalwd]))
254 254
255 255 self.originalwd = originalwd
256 256 self.target = target
257 257 self.state = state
258 258 self.skipped = skipped
259 259 self.collapsef = collapse
260 260 self.keepf = keep
261 261 self.keepbranchesf = keepbranches
262 262 self.external = external
263 263 self.activebookmark = activebookmark
264 264
265 265 def _handleskippingobsolete(self, rebaserevs, obsoleterevs, target):
266 266 """Compute structures necessary for skipping obsolete revisions
267 267
268 268 rebaserevs: iterable of all revisions that are to be rebased
269 269 obsoleterevs: iterable of all obsolete revisions in rebaseset
270 270 target: a destination revision for the rebase operation
271 271 """
272 272 self.obsoletenotrebased = {}
273 273 if not self.ui.configbool('experimental', 'rebaseskipobsolete',
274 274 default=True):
275 275 return
276 276 rebaseset = set(rebaserevs)
277 277 obsoleteset = set(obsoleterevs)
278 278 self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
279 279 obsoleteset, target)
280 280 skippedset = set(self.obsoletenotrebased)
281 281 _checkobsrebase(self.repo, self.ui, obsoleteset, rebaseset, skippedset)
282 282
283 283 def _prepareabortorcontinue(self, isabort):
284 284 try:
285 285 self.restorestatus()
286 286 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
287 287 except error.RepoLookupError:
288 288 if isabort:
289 289 clearstatus(self.repo)
290 290 clearcollapsemsg(self.repo)
291 291 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
292 292 ' only broken state is cleared)\n'))
293 293 return 0
294 294 else:
295 295 msg = _('cannot continue inconsistent rebase')
296 296 hint = _('use "hg rebase --abort" to clear broken state')
297 297 raise error.Abort(msg, hint=hint)
298 298 if isabort:
299 299 return abort(self.repo, self.originalwd, self.target,
300 300 self.state, activebookmark=self.activebookmark)
301 301
302 302 obsrevs = (r for r, st in self.state.items() if st == revprecursor)
303 303 self._handleskippingobsolete(self.state.keys(), obsrevs, self.target)
304 304
305 305 def _preparenewrebase(self, dest, rebaseset):
306 306 if dest is None:
307 307 return _nothingtorebase()
308 308
309 309 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
310 310 if (not (self.keepf or allowunstable)
311 311 and self.repo.revs('first(children(%ld) - %ld)',
312 312 rebaseset, rebaseset)):
313 313 raise error.Abort(
314 314 _("can't remove original changesets with"
315 315 " unrebased descendants"),
316 316 hint=_('use --keep to keep original changesets'))
317 317
318 318 obsrevs = _filterobsoleterevs(self.repo, set(rebaseset))
319 319 self._handleskippingobsolete(rebaseset, obsrevs, dest)
320 320
321 321 result = buildstate(self.repo, dest, rebaseset, self.collapsef,
322 322 self.obsoletenotrebased)
323 323
324 324 if not result:
325 325 # Empty state built, nothing to rebase
326 326 self.ui.status(_('nothing to rebase\n'))
327 327 return _nothingtorebase()
328 328
329 329 for root in self.repo.set('roots(%ld)', rebaseset):
330 330 if not self.keepf and not root.mutable():
331 331 raise error.Abort(_("can't rebase public changeset %s")
332 332 % root,
333 333 hint=_("see 'hg help phases' for details"))
334 334
335 335 (self.originalwd, self.target, self.state) = result
336 336 if self.collapsef:
337 337 self.targetancestors = self.repo.changelog.ancestors(
338 338 [self.target],
339 339 inclusive=True)
340 340 self.external = externalparent(self.repo, self.state,
341 341 self.targetancestors)
342 342
343 343 if dest.closesbranch() and not self.keepbranchesf:
344 344 self.ui.status(_('reopening closed branch head %s\n') % dest)
345 345
346 346 def _performrebase(self, tr):
347 347 repo, ui, opts = self.repo, self.ui, self.opts
348 348 if self.keepbranchesf:
349 349 # insert _savebranch at the start of extrafns so if
350 350 # there's a user-provided extrafn it can clobber branch if
351 351 # desired
352 352 self.extrafns.insert(0, _savebranch)
353 353 if self.collapsef:
354 354 branches = set()
355 355 for rev in self.state:
356 356 branches.add(repo[rev].branch())
357 357 if len(branches) > 1:
358 358 raise error.Abort(_('cannot collapse multiple named '
359 359 'branches'))
360 360
361 361 # Rebase
362 362 if not self.targetancestors:
363 363 self.targetancestors = repo.changelog.ancestors([self.target],
364 364 inclusive=True)
365 365
366 366 # Keep track of the current bookmarks in order to reset them later
367 367 self.currentbookmarks = repo._bookmarks.copy()
368 368 self.activebookmark = self.activebookmark or repo._activebookmark
369 369 if self.activebookmark:
370 370 bookmarks.deactivate(repo)
371 371
372 372 # Store the state before we begin so users can run 'hg rebase --abort'
373 373 # if we fail before the transaction closes.
374 374 self.storestatus()
375 375
376 376 sortedrevs = repo.revs('sort(%ld, -topo)', self.state)
377 377 cands = [k for k, v in self.state.iteritems() if v == revtodo]
378 378 total = len(cands)
379 379 pos = 0
380 380 for rev in sortedrevs:
381 381 ctx = repo[rev]
382 382 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
383 383 ctx.description().split('\n', 1)[0])
384 384 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
385 385 if names:
386 386 desc += ' (%s)' % ' '.join(names)
387 387 if self.state[rev] == revtodo:
388 388 pos += 1
389 389 ui.status(_('rebasing %s\n') % desc)
390 390 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
391 391 _('changesets'), total)
392 392 p1, p2, base = defineparents(repo, rev, self.target,
393 393 self.state,
394 394 self.targetancestors,
395 395 self.obsoletenotrebased)
396 396 self.storestatus(tr=tr)
397 397 storecollapsemsg(repo, self.collapsemsg)
398 398 if len(repo[None].parents()) == 2:
399 399 repo.ui.debug('resuming interrupted rebase\n')
400 400 else:
401 401 try:
402 402 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
403 403 'rebase')
404 404 stats = rebasenode(repo, rev, p1, base, self.state,
405 405 self.collapsef, self.target)
406 406 if stats and stats[3] > 0:
407 407 raise error.InterventionRequired(
408 408 _('unresolved conflicts (see hg '
409 409 'resolve, then hg rebase --continue)'))
410 410 finally:
411 411 ui.setconfig('ui', 'forcemerge', '', 'rebase')
412 412 if not self.collapsef:
413 413 merging = p2 != nullrev
414 414 editform = cmdutil.mergeeditform(merging, 'rebase')
415 415 editor = cmdutil.getcommiteditor(editform=editform, **opts)
416 416 newnode = concludenode(repo, rev, p1, p2,
417 417 extrafn=_makeextrafn(self.extrafns),
418 418 editor=editor,
419 419 keepbranches=self.keepbranchesf,
420 420 date=self.date)
421 421 else:
422 422 # Skip commit if we are collapsing
423 423 repo.dirstate.beginparentchange()
424 424 repo.setparents(repo[p1].node())
425 425 repo.dirstate.endparentchange()
426 426 newnode = None
427 427 # Update the state
428 428 if newnode is not None:
429 429 self.state[rev] = repo[newnode].rev()
430 430 ui.debug('rebased as %s\n' % short(newnode))
431 431 else:
432 432 if not self.collapsef:
433 433 ui.warn(_('note: rebase of %d:%s created no changes '
434 434 'to commit\n') % (rev, ctx))
435 435 self.skipped.add(rev)
436 436 self.state[rev] = p1
437 437 ui.debug('next revision set to %s\n' % p1)
438 438 elif self.state[rev] == nullmerge:
439 439 ui.debug('ignoring null merge rebase of %s\n' % rev)
440 440 elif self.state[rev] == revignored:
441 441 ui.status(_('not rebasing ignored %s\n') % desc)
442 442 elif self.state[rev] == revprecursor:
443 443 targetctx = repo[self.obsoletenotrebased[rev]]
444 444 desctarget = '%d:%s "%s"' % (targetctx.rev(), targetctx,
445 445 targetctx.description().split('\n', 1)[0])
446 446 msg = _('note: not rebasing %s, already in destination as %s\n')
447 447 ui.status(msg % (desc, desctarget))
448 448 elif self.state[rev] == revpruned:
449 449 msg = _('note: not rebasing %s, it has no successor\n')
450 450 ui.status(msg % desc)
451 451 else:
452 452 ui.status(_('already rebased %s as %s\n') %
453 453 (desc, repo[self.state[rev]]))
454 454
455 455 ui.progress(_('rebasing'), None)
456 456 ui.note(_('rebase merging completed\n'))
457 457
458 458 def _finishrebase(self):
459 459 repo, ui, opts = self.repo, self.ui, self.opts
460 460 if self.collapsef and not self.keepopen:
461 461 p1, p2, _base = defineparents(repo, min(self.state),
462 462 self.target, self.state,
463 463 self.targetancestors,
464 464 self.obsoletenotrebased)
465 465 editopt = opts.get('edit')
466 466 editform = 'rebase.collapse'
467 467 if self.collapsemsg:
468 468 commitmsg = self.collapsemsg
469 469 else:
470 470 commitmsg = 'Collapsed revision'
471 471 for rebased in self.state:
472 472 if rebased not in self.skipped and\
473 473 self.state[rebased] > nullmerge:
474 474 commitmsg += '\n* %s' % repo[rebased].description()
475 475 editopt = True
476 476 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
477 477 revtoreuse = max(self.state)
478 478 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
479 479 try:
480 480 newnode = concludenode(repo, revtoreuse, p1, self.external,
481 481 commitmsg=commitmsg,
482 482 extrafn=_makeextrafn(self.extrafns),
483 483 editor=editor,
484 484 keepbranches=self.keepbranchesf,
485 485 date=self.date)
486 486 dsguard.close()
487 487 release(dsguard)
488 488 except error.InterventionRequired:
489 489 dsguard.close()
490 490 release(dsguard)
491 491 raise
492 492 except Exception:
493 493 release(dsguard)
494 494 raise
495 495
496 496 if newnode is None:
497 497 newrev = self.target
498 498 else:
499 499 newrev = repo[newnode].rev()
500 500 for oldrev in self.state.iterkeys():
501 501 if self.state[oldrev] > nullmerge:
502 502 self.state[oldrev] = newrev
503 503
504 504 if 'qtip' in repo.tags():
505 505 updatemq(repo, self.state, self.skipped, **opts)
506 506
507 507 if self.currentbookmarks:
508 508 # Nodeids are needed to reset bookmarks
509 509 nstate = {}
510 510 for k, v in self.state.iteritems():
511 511 if v > nullmerge:
512 512 nstate[repo[k].node()] = repo[v].node()
513 513 elif v == revprecursor:
514 514 succ = self.obsoletenotrebased[k]
515 515 nstate[repo[k].node()] = repo[succ].node()
516 516 # XXX this is the same as dest.node() for the non-continue path --
517 517 # this should probably be cleaned up
518 518 targetnode = repo[self.target].node()
519 519
520 520 # restore original working directory
521 521 # (we do this before stripping)
522 522 newwd = self.state.get(self.originalwd, self.originalwd)
523 523 if newwd == revprecursor:
524 524 newwd = self.obsoletenotrebased[self.originalwd]
525 525 elif newwd < 0:
526 526 # original directory is a parent of rebase set root or ignored
527 527 newwd = self.originalwd
528 528 if newwd not in [c.rev() for c in repo[None].parents()]:
529 529 ui.note(_("update back to initial working directory parent\n"))
530 530 hg.updaterepo(repo, newwd, False)
531 531
532 532 if self.currentbookmarks:
533 533 with repo.transaction('bookmark') as tr:
534 534 updatebookmarks(repo, targetnode, nstate,
535 535 self.currentbookmarks, tr)
536 536 if self.activebookmark not in repo._bookmarks:
537 537 # active bookmark was divergent one and has been deleted
538 538 self.activebookmark = None
539 539
540 540 if not self.keepf:
541 541 collapsedas = None
542 542 if self.collapsef:
543 543 collapsedas = newnode
544 544 clearrebased(ui, repo, self.state, self.skipped, collapsedas)
545 545
546 546 clearstatus(repo)
547 547 clearcollapsemsg(repo)
548 548
549 549 ui.note(_("rebase completed\n"))
550 550 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
551 551 if self.skipped:
552 552 skippedlen = len(self.skipped)
553 553 ui.note(_("%d revisions have been skipped\n") % skippedlen)
554 554
555 555 if (self.activebookmark and
556 556 repo['.'].node() == repo._bookmarks[self.activebookmark]):
557 557 bookmarks.activate(repo, self.activebookmark)
558 558
559 559 @command('rebase',
560 560 [('s', 'source', '',
561 561 _('rebase the specified changeset and descendants'), _('REV')),
562 562 ('b', 'base', '',
563 563 _('rebase everything from branching point of specified changeset'),
564 564 _('REV')),
565 565 ('r', 'rev', [],
566 566 _('rebase these revisions'),
567 567 _('REV')),
568 568 ('d', 'dest', '',
569 569 _('rebase onto the specified changeset'), _('REV')),
570 570 ('', 'collapse', False, _('collapse the rebased changesets')),
571 571 ('m', 'message', '',
572 572 _('use text as collapse commit message'), _('TEXT')),
573 573 ('e', 'edit', False, _('invoke editor on commit messages')),
574 574 ('l', 'logfile', '',
575 575 _('read collapse commit message from file'), _('FILE')),
576 576 ('k', 'keep', False, _('keep original changesets')),
577 577 ('', 'keepbranches', False, _('keep original branch names')),
578 578 ('D', 'detach', False, _('(DEPRECATED)')),
579 579 ('i', 'interactive', False, _('(DEPRECATED)')),
580 580 ('t', 'tool', '', _('specify merge tool')),
581 581 ('c', 'continue', False, _('continue an interrupted rebase')),
582 582 ('a', 'abort', False, _('abort an interrupted rebase'))] +
583 583 templateopts,
584 584 _('[-s REV | -b REV] [-d REV] [OPTION]'))
585 585 def rebase(ui, repo, **opts):
586 586 """move changeset (and descendants) to a different branch
587 587
588 588 Rebase uses repeated merging to graft changesets from one part of
589 589 history (the source) onto another (the destination). This can be
590 590 useful for linearizing *local* changes relative to a master
591 591 development tree.
592 592
593 593 Published commits cannot be rebased (see :hg:`help phases`).
594 594 To copy commits, see :hg:`help graft`.
595 595
596 596 If you don't specify a destination changeset (``-d/--dest``), rebase
597 597 will use the same logic as :hg:`merge` to pick a destination. if
598 598 the current branch contains exactly one other head, the other head
599 599 is merged with by default. Otherwise, an explicit revision with
600 600 which to merge with must be provided. (destination changeset is not
601 601 modified by rebasing, but new changesets are added as its
602 602 descendants.)
603 603
604 604 Here are the ways to select changesets:
605 605
606 606 1. Explicitly select them using ``--rev``.
607 607
608 608 2. Use ``--source`` to select a root changeset and include all of its
609 609 descendants.
610 610
611 611 3. Use ``--base`` to select a changeset; rebase will find ancestors
612 612 and their descendants which are not also ancestors of the destination.
613 613
614 614 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
615 615 rebase will use ``--base .`` as above.
616 616
617 617 Rebase will destroy original changesets unless you use ``--keep``.
618 618 It will also move your bookmarks (even if you do).
619 619
620 620 Some changesets may be dropped if they do not contribute changes
621 621 (e.g. merges from the destination branch).
622 622
623 623 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
624 624 a named branch with two heads. You will need to explicitly specify source
625 625 and/or destination.
626 626
627 627 If you need to use a tool to automate merge/conflict decisions, you
628 628 can specify one with ``--tool``, see :hg:`help merge-tools`.
629 629 As a caveat: the tool will not be used to mediate when a file was
630 630 deleted, there is no hook presently available for this.
631 631
632 632 If a rebase is interrupted to manually resolve a conflict, it can be
633 633 continued with --continue/-c or aborted with --abort/-a.
634 634
635 635 .. container:: verbose
636 636
637 637 Examples:
638 638
639 639 - move "local changes" (current commit back to branching point)
640 640 to the current branch tip after a pull::
641 641
642 642 hg rebase
643 643
644 644 - move a single changeset to the stable branch::
645 645
646 646 hg rebase -r 5f493448 -d stable
647 647
648 648 - splice a commit and all its descendants onto another part of history::
649 649
650 650 hg rebase --source c0c3 --dest 4cf9
651 651
652 652 - rebase everything on a branch marked by a bookmark onto the
653 653 default branch::
654 654
655 655 hg rebase --base myfeature --dest default
656 656
657 657 - collapse a sequence of changes into a single commit::
658 658
659 659 hg rebase --collapse -r 1520:1525 -d .
660 660
661 661 - move a named branch while preserving its name::
662 662
663 663 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
664 664
665 Configuration Options:
666
667 You can make rebase require a destination if you set the following config
668 option:
669
670 [commands]
671 rebase.requiredest = False
672
673 Return Values:
674
665 675 Returns 0 on success, 1 if nothing to rebase or there are
666 676 unresolved conflicts.
667 677
668 678 """
669 679 rbsrt = rebaseruntime(repo, ui, opts)
670 680
671 681 lock = wlock = None
672 682 try:
673 683 wlock = repo.wlock()
674 684 lock = repo.lock()
675 685
676 686 # Validate input and define rebasing points
677 687 destf = opts.get('dest', None)
688
689 if ui.config('commands', 'rebase.requiredest', False):
690 if not destf:
691 raise error.Abort(_('you must specify a destination'),
692 hint=_('use: hg rebase -d REV'))
693
678 694 srcf = opts.get('source', None)
679 695 basef = opts.get('base', None)
680 696 revf = opts.get('rev', [])
681 697 # search default destination in this space
682 698 # used in the 'hg pull --rebase' case, see issue 5214.
683 699 destspace = opts.get('_destspace')
684 700 contf = opts.get('continue')
685 701 abortf = opts.get('abort')
686 702 if opts.get('interactive'):
687 703 try:
688 704 if extensions.find('histedit'):
689 705 enablehistedit = ''
690 706 except KeyError:
691 707 enablehistedit = " --config extensions.histedit="
692 708 help = "hg%s help -e histedit" % enablehistedit
693 709 msg = _("interactive history editing is supported by the "
694 710 "'histedit' extension (see \"%s\")") % help
695 711 raise error.Abort(msg)
696 712
697 713 if rbsrt.collapsemsg and not rbsrt.collapsef:
698 714 raise error.Abort(
699 715 _('message can only be specified with collapse'))
700 716
701 717 if contf or abortf:
702 718 if contf and abortf:
703 719 raise error.Abort(_('cannot use both abort and continue'))
704 720 if rbsrt.collapsef:
705 721 raise error.Abort(
706 722 _('cannot use collapse with continue or abort'))
707 723 if srcf or basef or destf:
708 724 raise error.Abort(
709 725 _('abort and continue do not allow specifying revisions'))
710 726 if abortf and opts.get('tool', False):
711 727 ui.warn(_('tool option will be ignored\n'))
712 728 if contf:
713 729 ms = mergemod.mergestate.read(repo)
714 730 mergeutil.checkunresolved(ms)
715 731
716 732 retcode = rbsrt._prepareabortorcontinue(abortf)
717 733 if retcode is not None:
718 734 return retcode
719 735 else:
720 736 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
721 737 destspace=destspace)
722 738 retcode = rbsrt._preparenewrebase(dest, rebaseset)
723 739 if retcode is not None:
724 740 return retcode
725 741
726 742 with repo.transaction('rebase') as tr:
727 743 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
728 744 try:
729 745 rbsrt._performrebase(tr)
730 746 dsguard.close()
731 747 release(dsguard)
732 748 except error.InterventionRequired:
733 749 dsguard.close()
734 750 release(dsguard)
735 751 tr.close()
736 752 raise
737 753 except Exception:
738 754 release(dsguard)
739 755 raise
740 756 rbsrt._finishrebase()
741 757 finally:
742 758 release(lock, wlock)
743 759
744 760 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=None,
745 761 destspace=None):
746 762 """use revisions argument to define destination and rebase set
747 763 """
748 764 if revf is None:
749 765 revf = []
750 766
751 767 # destspace is here to work around issues with `hg pull --rebase` see
752 768 # issue5214 for details
753 769 if srcf and basef:
754 770 raise error.Abort(_('cannot specify both a source and a base'))
755 771 if revf and basef:
756 772 raise error.Abort(_('cannot specify both a revision and a base'))
757 773 if revf and srcf:
758 774 raise error.Abort(_('cannot specify both a revision and a source'))
759 775
760 776 cmdutil.checkunfinished(repo)
761 777 cmdutil.bailifchanged(repo)
762 778
763 779 if destf:
764 780 dest = scmutil.revsingle(repo, destf)
765 781
766 782 if revf:
767 783 rebaseset = scmutil.revrange(repo, revf)
768 784 if not rebaseset:
769 785 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
770 786 return None, None
771 787 elif srcf:
772 788 src = scmutil.revrange(repo, [srcf])
773 789 if not src:
774 790 ui.status(_('empty "source" revision set - nothing to rebase\n'))
775 791 return None, None
776 792 rebaseset = repo.revs('(%ld)::', src)
777 793 assert rebaseset
778 794 else:
779 795 base = scmutil.revrange(repo, [basef or '.'])
780 796 if not base:
781 797 ui.status(_('empty "base" revision set - '
782 798 "can't compute rebase set\n"))
783 799 return None, None
784 800 if not destf:
785 801 dest = repo[_destrebase(repo, base, destspace=destspace)]
786 802 destf = str(dest)
787 803
788 804 roots = [] # selected children of branching points
789 805 bpbase = {} # {branchingpoint: [origbase]}
790 806 for b in base: # group bases by branching points
791 807 bp = repo.revs('ancestor(%d, %d)', b, dest).first()
792 808 bpbase[bp] = bpbase.get(bp, []) + [b]
793 809 if None in bpbase:
794 810 # emulate the old behavior, showing "nothing to rebase" (a better
795 811 # behavior may be abort with "cannot find branching point" error)
796 812 bpbase.clear()
797 813 for bp, bs in bpbase.iteritems(): # calculate roots
798 814 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
799 815
800 816 rebaseset = repo.revs('%ld::', roots)
801 817
802 818 if not rebaseset:
803 819 # transform to list because smartsets are not comparable to
804 820 # lists. This should be improved to honor laziness of
805 821 # smartset.
806 822 if list(base) == [dest.rev()]:
807 823 if basef:
808 824 ui.status(_('nothing to rebase - %s is both "base"'
809 825 ' and destination\n') % dest)
810 826 else:
811 827 ui.status(_('nothing to rebase - working directory '
812 828 'parent is also destination\n'))
813 829 elif not repo.revs('%ld - ::%d', base, dest):
814 830 if basef:
815 831 ui.status(_('nothing to rebase - "base" %s is '
816 832 'already an ancestor of destination '
817 833 '%s\n') %
818 834 ('+'.join(str(repo[r]) for r in base),
819 835 dest))
820 836 else:
821 837 ui.status(_('nothing to rebase - working '
822 838 'directory parent is already an '
823 839 'ancestor of destination %s\n') % dest)
824 840 else: # can it happen?
825 841 ui.status(_('nothing to rebase from %s to %s\n') %
826 842 ('+'.join(str(repo[r]) for r in base), dest))
827 843 return None, None
828 844
829 845 if not destf:
830 846 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
831 847 destf = str(dest)
832 848
833 849 return dest, rebaseset
834 850
835 851 def externalparent(repo, state, targetancestors):
836 852 """Return the revision that should be used as the second parent
837 853 when the revisions in state is collapsed on top of targetancestors.
838 854 Abort if there is more than one parent.
839 855 """
840 856 parents = set()
841 857 source = min(state)
842 858 for rev in state:
843 859 if rev == source:
844 860 continue
845 861 for p in repo[rev].parents():
846 862 if (p.rev() not in state
847 863 and p.rev() not in targetancestors):
848 864 parents.add(p.rev())
849 865 if not parents:
850 866 return nullrev
851 867 if len(parents) == 1:
852 868 return parents.pop()
853 869 raise error.Abort(_('unable to collapse on top of %s, there is more '
854 870 'than one external parent: %s') %
855 871 (max(targetancestors),
856 872 ', '.join(str(p) for p in sorted(parents))))
857 873
858 874 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
859 875 keepbranches=False, date=None):
860 876 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
861 877 but also store useful information in extra.
862 878 Return node of committed revision.'''
863 879 repo.setparents(repo[p1].node(), repo[p2].node())
864 880 ctx = repo[rev]
865 881 if commitmsg is None:
866 882 commitmsg = ctx.description()
867 883 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
868 884 extra = {'rebase_source': ctx.hex()}
869 885 if extrafn:
870 886 extrafn(ctx, extra)
871 887
872 888 targetphase = max(ctx.phase(), phases.draft)
873 889 overrides = {('phases', 'new-commit'): targetphase}
874 890 with repo.ui.configoverride(overrides, 'rebase'):
875 891 if keepbranch:
876 892 repo.ui.setconfig('ui', 'allowemptycommit', True)
877 893 # Commit might fail if unresolved files exist
878 894 if date is None:
879 895 date = ctx.date()
880 896 newnode = repo.commit(text=commitmsg, user=ctx.user(),
881 897 date=date, extra=extra, editor=editor)
882 898
883 899 repo.dirstate.setbranch(repo[newnode].branch())
884 900 return newnode
885 901
886 902 def rebasenode(repo, rev, p1, base, state, collapse, target):
887 903 'Rebase a single revision rev on top of p1 using base as merge ancestor'
888 904 # Merge phase
889 905 # Update to target and merge it with local
890 906 if repo['.'].rev() != p1:
891 907 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
892 908 mergemod.update(repo, p1, False, True)
893 909 else:
894 910 repo.ui.debug(" already in target\n")
895 911 repo.dirstate.write(repo.currenttransaction())
896 912 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
897 913 if base is not None:
898 914 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
899 915 # When collapsing in-place, the parent is the common ancestor, we
900 916 # have to allow merging with it.
901 917 stats = mergemod.update(repo, rev, True, True, base, collapse,
902 918 labels=['dest', 'source'])
903 919 if collapse:
904 920 copies.duplicatecopies(repo, rev, target)
905 921 else:
906 922 # If we're not using --collapse, we need to
907 923 # duplicate copies between the revision we're
908 924 # rebasing and its first parent, but *not*
909 925 # duplicate any copies that have already been
910 926 # performed in the destination.
911 927 p1rev = repo[rev].p1().rev()
912 928 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
913 929 return stats
914 930
915 931 def nearestrebased(repo, rev, state):
916 932 """return the nearest ancestors of rev in the rebase result"""
917 933 rebased = [r for r in state if state[r] > nullmerge]
918 934 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
919 935 if candidates:
920 936 return state[candidates.first()]
921 937 else:
922 938 return None
923 939
924 940 def _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs, rebaseobsskipped):
925 941 """
926 942 Abort if rebase will create divergence or rebase is noop because of markers
927 943
928 944 `rebaseobsrevs`: set of obsolete revision in source
929 945 `rebasesetrevs`: set of revisions to be rebased from source
930 946 `rebaseobsskipped`: set of revisions from source skipped because they have
931 947 successors in destination
932 948 """
933 949 # Obsolete node with successors not in dest leads to divergence
934 950 divergenceok = ui.configbool('experimental',
935 951 'allowdivergence')
936 952 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
937 953
938 954 if divergencebasecandidates and not divergenceok:
939 955 divhashes = (str(repo[r])
940 956 for r in divergencebasecandidates)
941 957 msg = _("this rebase will cause "
942 958 "divergences from: %s")
943 959 h = _("to force the rebase please set "
944 960 "experimental.allowdivergence=True")
945 961 raise error.Abort(msg % (",".join(divhashes),), hint=h)
946 962
947 963 def defineparents(repo, rev, target, state, targetancestors,
948 964 obsoletenotrebased):
949 965 'Return the new parent relationship of the revision that will be rebased'
950 966 parents = repo[rev].parents()
951 967 p1 = p2 = nullrev
952 968 rp1 = None
953 969
954 970 p1n = parents[0].rev()
955 971 if p1n in targetancestors:
956 972 p1 = target
957 973 elif p1n in state:
958 974 if state[p1n] == nullmerge:
959 975 p1 = target
960 976 elif state[p1n] in revskipped:
961 977 p1 = nearestrebased(repo, p1n, state)
962 978 if p1 is None:
963 979 p1 = target
964 980 else:
965 981 p1 = state[p1n]
966 982 else: # p1n external
967 983 p1 = target
968 984 p2 = p1n
969 985
970 986 if len(parents) == 2 and parents[1].rev() not in targetancestors:
971 987 p2n = parents[1].rev()
972 988 # interesting second parent
973 989 if p2n in state:
974 990 if p1 == target: # p1n in targetancestors or external
975 991 p1 = state[p2n]
976 992 if p1 == revprecursor:
977 993 rp1 = obsoletenotrebased[p2n]
978 994 elif state[p2n] in revskipped:
979 995 p2 = nearestrebased(repo, p2n, state)
980 996 if p2 is None:
981 997 # no ancestors rebased yet, detach
982 998 p2 = target
983 999 else:
984 1000 p2 = state[p2n]
985 1001 else: # p2n external
986 1002 if p2 != nullrev: # p1n external too => rev is a merged revision
987 1003 raise error.Abort(_('cannot use revision %d as base, result '
988 1004 'would have 3 parents') % rev)
989 1005 p2 = p2n
990 1006 repo.ui.debug(" future parents are %d and %d\n" %
991 1007 (repo[rp1 or p1].rev(), repo[p2].rev()))
992 1008
993 1009 if not any(p.rev() in state for p in parents):
994 1010 # Case (1) root changeset of a non-detaching rebase set.
995 1011 # Let the merge mechanism find the base itself.
996 1012 base = None
997 1013 elif not repo[rev].p2():
998 1014 # Case (2) detaching the node with a single parent, use this parent
999 1015 base = repo[rev].p1().rev()
1000 1016 else:
1001 1017 # Assuming there is a p1, this is the case where there also is a p2.
1002 1018 # We are thus rebasing a merge and need to pick the right merge base.
1003 1019 #
1004 1020 # Imagine we have:
1005 1021 # - M: current rebase revision in this step
1006 1022 # - A: one parent of M
1007 1023 # - B: other parent of M
1008 1024 # - D: destination of this merge step (p1 var)
1009 1025 #
1010 1026 # Consider the case where D is a descendant of A or B and the other is
1011 1027 # 'outside'. In this case, the right merge base is the D ancestor.
1012 1028 #
1013 1029 # An informal proof, assuming A is 'outside' and B is the D ancestor:
1014 1030 #
1015 1031 # If we pick B as the base, the merge involves:
1016 1032 # - changes from B to M (actual changeset payload)
1017 1033 # - changes from B to D (induced by rebase) as D is a rebased
1018 1034 # version of B)
1019 1035 # Which exactly represent the rebase operation.
1020 1036 #
1021 1037 # If we pick A as the base, the merge involves:
1022 1038 # - changes from A to M (actual changeset payload)
1023 1039 # - changes from A to D (with include changes between unrelated A and B
1024 1040 # plus changes induced by rebase)
1025 1041 # Which does not represent anything sensible and creates a lot of
1026 1042 # conflicts. A is thus not the right choice - B is.
1027 1043 #
1028 1044 # Note: The base found in this 'proof' is only correct in the specified
1029 1045 # case. This base does not make sense if is not D a descendant of A or B
1030 1046 # or if the other is not parent 'outside' (especially not if the other
1031 1047 # parent has been rebased). The current implementation does not
1032 1048 # make it feasible to consider different cases separately. In these
1033 1049 # other cases we currently just leave it to the user to correctly
1034 1050 # resolve an impossible merge using a wrong ancestor.
1035 1051 #
1036 1052 # xx, p1 could be -4, and both parents could probably be -4...
1037 1053 for p in repo[rev].parents():
1038 1054 if state.get(p.rev()) == p1:
1039 1055 base = p.rev()
1040 1056 break
1041 1057 else: # fallback when base not found
1042 1058 base = None
1043 1059
1044 1060 # Raise because this function is called wrong (see issue 4106)
1045 1061 raise AssertionError('no base found to rebase on '
1046 1062 '(defineparents called wrong)')
1047 1063 return rp1 or p1, p2, base
1048 1064
1049 1065 def isagitpatch(repo, patchname):
1050 1066 'Return true if the given patch is in git format'
1051 1067 mqpatch = os.path.join(repo.mq.path, patchname)
1052 1068 for line in patch.linereader(file(mqpatch, 'rb')):
1053 1069 if line.startswith('diff --git'):
1054 1070 return True
1055 1071 return False
1056 1072
1057 1073 def updatemq(repo, state, skipped, **opts):
1058 1074 'Update rebased mq patches - finalize and then import them'
1059 1075 mqrebase = {}
1060 1076 mq = repo.mq
1061 1077 original_series = mq.fullseries[:]
1062 1078 skippedpatches = set()
1063 1079
1064 1080 for p in mq.applied:
1065 1081 rev = repo[p.node].rev()
1066 1082 if rev in state:
1067 1083 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1068 1084 (rev, p.name))
1069 1085 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1070 1086 else:
1071 1087 # Applied but not rebased, not sure this should happen
1072 1088 skippedpatches.add(p.name)
1073 1089
1074 1090 if mqrebase:
1075 1091 mq.finish(repo, mqrebase.keys())
1076 1092
1077 1093 # We must start import from the newest revision
1078 1094 for rev in sorted(mqrebase, reverse=True):
1079 1095 if rev not in skipped:
1080 1096 name, isgit = mqrebase[rev]
1081 1097 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1082 1098 (name, state[rev], repo[state[rev]]))
1083 1099 mq.qimport(repo, (), patchname=name, git=isgit,
1084 1100 rev=[str(state[rev])])
1085 1101 else:
1086 1102 # Rebased and skipped
1087 1103 skippedpatches.add(mqrebase[rev][0])
1088 1104
1089 1105 # Patches were either applied and rebased and imported in
1090 1106 # order, applied and removed or unapplied. Discard the removed
1091 1107 # ones while preserving the original series order and guards.
1092 1108 newseries = [s for s in original_series
1093 1109 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1094 1110 mq.fullseries[:] = newseries
1095 1111 mq.seriesdirty = True
1096 1112 mq.savedirty()
1097 1113
1098 1114 def updatebookmarks(repo, targetnode, nstate, originalbookmarks, tr):
1099 1115 'Move bookmarks to their correct changesets, and delete divergent ones'
1100 1116 marks = repo._bookmarks
1101 1117 for k, v in originalbookmarks.iteritems():
1102 1118 if v in nstate:
1103 1119 # update the bookmarks for revs that have moved
1104 1120 marks[k] = nstate[v]
1105 1121 bookmarks.deletedivergent(repo, [targetnode], k)
1106 1122 marks.recordchange(tr)
1107 1123
1108 1124 def storecollapsemsg(repo, collapsemsg):
1109 1125 'Store the collapse message to allow recovery'
1110 1126 collapsemsg = collapsemsg or ''
1111 1127 f = repo.vfs("last-message.txt", "w")
1112 1128 f.write("%s\n" % collapsemsg)
1113 1129 f.close()
1114 1130
1115 1131 def clearcollapsemsg(repo):
1116 1132 'Remove collapse message file'
1117 1133 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1118 1134
1119 1135 def restorecollapsemsg(repo, isabort):
1120 1136 'Restore previously stored collapse message'
1121 1137 try:
1122 1138 f = repo.vfs("last-message.txt")
1123 1139 collapsemsg = f.readline().strip()
1124 1140 f.close()
1125 1141 except IOError as err:
1126 1142 if err.errno != errno.ENOENT:
1127 1143 raise
1128 1144 if isabort:
1129 1145 # Oh well, just abort like normal
1130 1146 collapsemsg = ''
1131 1147 else:
1132 1148 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1133 1149 return collapsemsg
1134 1150
1135 1151 def clearstatus(repo):
1136 1152 'Remove the status files'
1137 1153 _clearrebasesetvisibiliy(repo)
1138 1154 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1139 1155
1140 1156 def needupdate(repo, state):
1141 1157 '''check whether we should `update --clean` away from a merge, or if
1142 1158 somehow the working dir got forcibly updated, e.g. by older hg'''
1143 1159 parents = [p.rev() for p in repo[None].parents()]
1144 1160
1145 1161 # Are we in a merge state at all?
1146 1162 if len(parents) < 2:
1147 1163 return False
1148 1164
1149 1165 # We should be standing on the first as-of-yet unrebased commit.
1150 1166 firstunrebased = min([old for old, new in state.iteritems()
1151 1167 if new == nullrev])
1152 1168 if firstunrebased in parents:
1153 1169 return True
1154 1170
1155 1171 return False
1156 1172
1157 1173 def abort(repo, originalwd, target, state, activebookmark=None):
1158 1174 '''Restore the repository to its original state. Additional args:
1159 1175
1160 1176 activebookmark: the name of the bookmark that should be active after the
1161 1177 restore'''
1162 1178
1163 1179 try:
1164 1180 # If the first commits in the rebased set get skipped during the rebase,
1165 1181 # their values within the state mapping will be the target rev id. The
1166 1182 # dstates list must must not contain the target rev (issue4896)
1167 1183 dstates = [s for s in state.values() if s >= 0 and s != target]
1168 1184 immutable = [d for d in dstates if not repo[d].mutable()]
1169 1185 cleanup = True
1170 1186 if immutable:
1171 1187 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1172 1188 % ', '.join(str(repo[r]) for r in immutable),
1173 1189 hint=_("see 'hg help phases' for details"))
1174 1190 cleanup = False
1175 1191
1176 1192 descendants = set()
1177 1193 if dstates:
1178 1194 descendants = set(repo.changelog.descendants(dstates))
1179 1195 if descendants - set(dstates):
1180 1196 repo.ui.warn(_("warning: new changesets detected on target branch, "
1181 1197 "can't strip\n"))
1182 1198 cleanup = False
1183 1199
1184 1200 if cleanup:
1185 1201 shouldupdate = False
1186 1202 rebased = filter(lambda x: x >= 0 and x != target, state.values())
1187 1203 if rebased:
1188 1204 strippoints = [
1189 1205 c.node() for c in repo.set('roots(%ld)', rebased)]
1190 1206
1191 1207 updateifonnodes = set(rebased)
1192 1208 updateifonnodes.add(target)
1193 1209 updateifonnodes.add(originalwd)
1194 1210 shouldupdate = repo['.'].rev() in updateifonnodes
1195 1211
1196 1212 # Update away from the rebase if necessary
1197 1213 if shouldupdate or needupdate(repo, state):
1198 1214 mergemod.update(repo, originalwd, False, True)
1199 1215
1200 1216 # Strip from the first rebased revision
1201 1217 if rebased:
1202 1218 # no backup of rebased cset versions needed
1203 1219 repair.strip(repo.ui, repo, strippoints)
1204 1220
1205 1221 if activebookmark and activebookmark in repo._bookmarks:
1206 1222 bookmarks.activate(repo, activebookmark)
1207 1223
1208 1224 finally:
1209 1225 clearstatus(repo)
1210 1226 clearcollapsemsg(repo)
1211 1227 repo.ui.warn(_('rebase aborted\n'))
1212 1228 return 0
1213 1229
1214 1230 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1215 1231 '''Define which revisions are going to be rebased and where
1216 1232
1217 1233 repo: repo
1218 1234 dest: context
1219 1235 rebaseset: set of rev
1220 1236 '''
1221 1237 originalwd = repo['.'].rev()
1222 1238 _setrebasesetvisibility(repo, set(rebaseset) | set([originalwd]))
1223 1239
1224 1240 # This check isn't strictly necessary, since mq detects commits over an
1225 1241 # applied patch. But it prevents messing up the working directory when
1226 1242 # a partially completed rebase is blocked by mq.
1227 1243 if 'qtip' in repo.tags() and (dest.node() in
1228 1244 [s.node for s in repo.mq.applied]):
1229 1245 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1230 1246
1231 1247 roots = list(repo.set('roots(%ld)', rebaseset))
1232 1248 if not roots:
1233 1249 raise error.Abort(_('no matching revisions'))
1234 1250 roots.sort()
1235 1251 state = {}
1236 1252 detachset = set()
1237 1253 for root in roots:
1238 1254 commonbase = root.ancestor(dest)
1239 1255 if commonbase == root:
1240 1256 raise error.Abort(_('source is ancestor of destination'))
1241 1257 if commonbase == dest:
1242 1258 wctx = repo[None]
1243 1259 if dest == wctx.p1():
1244 1260 # when rebasing to '.', it will use the current wd branch name
1245 1261 samebranch = root.branch() == wctx.branch()
1246 1262 else:
1247 1263 samebranch = root.branch() == dest.branch()
1248 1264 if not collapse and samebranch and root in dest.children():
1249 1265 repo.ui.debug('source is a child of destination\n')
1250 1266 return None
1251 1267
1252 1268 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1253 1269 state.update(dict.fromkeys(rebaseset, revtodo))
1254 1270 # Rebase tries to turn <dest> into a parent of <root> while
1255 1271 # preserving the number of parents of rebased changesets:
1256 1272 #
1257 1273 # - A changeset with a single parent will always be rebased as a
1258 1274 # changeset with a single parent.
1259 1275 #
1260 1276 # - A merge will be rebased as merge unless its parents are both
1261 1277 # ancestors of <dest> or are themselves in the rebased set and
1262 1278 # pruned while rebased.
1263 1279 #
1264 1280 # If one parent of <root> is an ancestor of <dest>, the rebased
1265 1281 # version of this parent will be <dest>. This is always true with
1266 1282 # --base option.
1267 1283 #
1268 1284 # Otherwise, we need to *replace* the original parents with
1269 1285 # <dest>. This "detaches" the rebased set from its former location
1270 1286 # and rebases it onto <dest>. Changes introduced by ancestors of
1271 1287 # <root> not common with <dest> (the detachset, marked as
1272 1288 # nullmerge) are "removed" from the rebased changesets.
1273 1289 #
1274 1290 # - If <root> has a single parent, set it to <dest>.
1275 1291 #
1276 1292 # - If <root> is a merge, we cannot decide which parent to
1277 1293 # replace, the rebase operation is not clearly defined.
1278 1294 #
1279 1295 # The table below sums up this behavior:
1280 1296 #
1281 1297 # +------------------+----------------------+-------------------------+
1282 1298 # | | one parent | merge |
1283 1299 # +------------------+----------------------+-------------------------+
1284 1300 # | parent in | new parent is <dest> | parents in ::<dest> are |
1285 1301 # | ::<dest> | | remapped to <dest> |
1286 1302 # +------------------+----------------------+-------------------------+
1287 1303 # | unrelated source | new parent is <dest> | ambiguous, abort |
1288 1304 # +------------------+----------------------+-------------------------+
1289 1305 #
1290 1306 # The actual abort is handled by `defineparents`
1291 1307 if len(root.parents()) <= 1:
1292 1308 # ancestors of <root> not ancestors of <dest>
1293 1309 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1294 1310 [root.rev()]))
1295 1311 for r in detachset:
1296 1312 if r not in state:
1297 1313 state[r] = nullmerge
1298 1314 if len(roots) > 1:
1299 1315 # If we have multiple roots, we may have "hole" in the rebase set.
1300 1316 # Rebase roots that descend from those "hole" should not be detached as
1301 1317 # other root are. We use the special `revignored` to inform rebase that
1302 1318 # the revision should be ignored but that `defineparents` should search
1303 1319 # a rebase destination that make sense regarding rebased topology.
1304 1320 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1305 1321 for ignored in set(rebasedomain) - set(rebaseset):
1306 1322 state[ignored] = revignored
1307 1323 for r in obsoletenotrebased:
1308 1324 if obsoletenotrebased[r] is None:
1309 1325 state[r] = revpruned
1310 1326 else:
1311 1327 state[r] = revprecursor
1312 1328 return originalwd, dest.rev(), state
1313 1329
1314 1330 def clearrebased(ui, repo, state, skipped, collapsedas=None):
1315 1331 """dispose of rebased revision at the end of the rebase
1316 1332
1317 1333 If `collapsedas` is not None, the rebase was a collapse whose result if the
1318 1334 `collapsedas` node."""
1319 1335 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1320 1336 markers = []
1321 1337 for rev, newrev in sorted(state.items()):
1322 1338 if newrev >= 0:
1323 1339 if rev in skipped:
1324 1340 succs = ()
1325 1341 elif collapsedas is not None:
1326 1342 succs = (repo[collapsedas],)
1327 1343 else:
1328 1344 succs = (repo[newrev],)
1329 1345 markers.append((repo[rev], succs))
1330 1346 if markers:
1331 1347 obsolete.createmarkers(repo, markers)
1332 1348 else:
1333 1349 rebased = [rev for rev in state if state[rev] > nullmerge]
1334 1350 if rebased:
1335 1351 stripped = []
1336 1352 for root in repo.set('roots(%ld)', rebased):
1337 1353 if set(repo.changelog.descendants([root.rev()])) - set(state):
1338 1354 ui.warn(_("warning: new changesets detected "
1339 1355 "on source branch, not stripping\n"))
1340 1356 else:
1341 1357 stripped.append(root.node())
1342 1358 if stripped:
1343 1359 # backup the old csets by default
1344 1360 repair.strip(ui, repo, stripped, "all")
1345 1361
1346 1362
1347 1363 def pullrebase(orig, ui, repo, *args, **opts):
1348 1364 'Call rebase after pull if the latter has been invoked with --rebase'
1349 1365 ret = None
1350 1366 if opts.get('rebase'):
1351 1367 wlock = lock = None
1352 1368 try:
1353 1369 wlock = repo.wlock()
1354 1370 lock = repo.lock()
1355 1371 if opts.get('update'):
1356 1372 del opts['update']
1357 1373 ui.debug('--update and --rebase are not compatible, ignoring '
1358 1374 'the update flag\n')
1359 1375
1360 1376 cmdutil.checkunfinished(repo)
1361 1377 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1362 1378 'please commit or shelve your changes first'))
1363 1379
1364 1380 revsprepull = len(repo)
1365 1381 origpostincoming = commands.postincoming
1366 1382 def _dummy(*args, **kwargs):
1367 1383 pass
1368 1384 commands.postincoming = _dummy
1369 1385 try:
1370 1386 ret = orig(ui, repo, *args, **opts)
1371 1387 finally:
1372 1388 commands.postincoming = origpostincoming
1373 1389 revspostpull = len(repo)
1374 1390 if revspostpull > revsprepull:
1375 1391 # --rev option from pull conflict with rebase own --rev
1376 1392 # dropping it
1377 1393 if 'rev' in opts:
1378 1394 del opts['rev']
1379 1395 # positional argument from pull conflicts with rebase's own
1380 1396 # --source.
1381 1397 if 'source' in opts:
1382 1398 del opts['source']
1383 1399 # revsprepull is the len of the repo, not revnum of tip.
1384 1400 destspace = list(repo.changelog.revs(start=revsprepull))
1385 1401 opts['_destspace'] = destspace
1386 1402 try:
1387 1403 rebase(ui, repo, **opts)
1388 1404 except error.NoMergeDestAbort:
1389 1405 # we can maybe update instead
1390 1406 rev, _a, _b = destutil.destupdate(repo)
1391 1407 if rev == repo['.'].rev():
1392 1408 ui.status(_('nothing to rebase\n'))
1393 1409 else:
1394 1410 ui.status(_('nothing to rebase - updating instead\n'))
1395 1411 # not passing argument to get the bare update behavior
1396 1412 # with warning and trumpets
1397 1413 commands.update(ui, repo)
1398 1414 finally:
1399 1415 release(lock, wlock)
1400 1416 else:
1401 1417 if opts.get('tool'):
1402 1418 raise error.Abort(_('--tool can only be used with --rebase'))
1403 1419 ret = orig(ui, repo, *args, **opts)
1404 1420
1405 1421 return ret
1406 1422
1407 1423 def _setrebasesetvisibility(repo, revs):
1408 1424 """store the currently rebased set on the repo object
1409 1425
1410 1426 This is used by another function to prevent rebased revision to because
1411 1427 hidden (see issue4504)"""
1412 1428 repo = repo.unfiltered()
1413 1429 repo._rebaseset = revs
1414 1430 # invalidate cache if visibility changes
1415 1431 hiddens = repo.filteredrevcache.get('visible', set())
1416 1432 if revs & hiddens:
1417 1433 repo.invalidatevolatilesets()
1418 1434
1419 1435 def _clearrebasesetvisibiliy(repo):
1420 1436 """remove rebaseset data from the repo"""
1421 1437 repo = repo.unfiltered()
1422 1438 if '_rebaseset' in vars(repo):
1423 1439 del repo._rebaseset
1424 1440
1425 1441 def _rebasedvisible(orig, repo):
1426 1442 """ensure rebased revs stay visible (see issue4504)"""
1427 1443 blockers = orig(repo)
1428 1444 blockers.update(getattr(repo, '_rebaseset', ()))
1429 1445 return blockers
1430 1446
1431 1447 def _filterobsoleterevs(repo, revs):
1432 1448 """returns a set of the obsolete revisions in revs"""
1433 1449 return set(r for r in revs if repo[r].obsolete())
1434 1450
1435 1451 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1436 1452 """return a mapping obsolete => successor for all obsolete nodes to be
1437 1453 rebased that have a successors in the destination
1438 1454
1439 1455 obsolete => None entries in the mapping indicate nodes with no successor"""
1440 1456 obsoletenotrebased = {}
1441 1457
1442 1458 # Build a mapping successor => obsolete nodes for the obsolete
1443 1459 # nodes to be rebased
1444 1460 allsuccessors = {}
1445 1461 cl = repo.changelog
1446 1462 for r in rebaseobsrevs:
1447 1463 node = cl.node(r)
1448 1464 for s in obsolete.allsuccessors(repo.obsstore, [node]):
1449 1465 try:
1450 1466 allsuccessors[cl.rev(s)] = cl.rev(node)
1451 1467 except LookupError:
1452 1468 pass
1453 1469
1454 1470 if allsuccessors:
1455 1471 # Look for successors of obsolete nodes to be rebased among
1456 1472 # the ancestors of dest
1457 1473 ancs = cl.ancestors([repo[dest].rev()],
1458 1474 stoprev=min(allsuccessors),
1459 1475 inclusive=True)
1460 1476 for s in allsuccessors:
1461 1477 if s in ancs:
1462 1478 obsoletenotrebased[allsuccessors[s]] = s
1463 1479 elif (s == allsuccessors[s] and
1464 1480 allsuccessors.values().count(s) == 1):
1465 1481 # plain prune
1466 1482 obsoletenotrebased[s] = None
1467 1483
1468 1484 return obsoletenotrebased
1469 1485
1470 1486 def summaryhook(ui, repo):
1471 1487 if not repo.vfs.exists('rebasestate'):
1472 1488 return
1473 1489 try:
1474 1490 rbsrt = rebaseruntime(repo, ui, {})
1475 1491 rbsrt.restorestatus()
1476 1492 state = rbsrt.state
1477 1493 except error.RepoLookupError:
1478 1494 # i18n: column positioning for "hg summary"
1479 1495 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1480 1496 ui.write(msg)
1481 1497 return
1482 1498 numrebased = len([i for i in state.itervalues() if i >= 0])
1483 1499 # i18n: column positioning for "hg summary"
1484 1500 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1485 1501 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1486 1502 ui.label(_('%d remaining'), 'rebase.remaining') %
1487 1503 (len(state) - numrebased)))
1488 1504
1489 1505 def uisetup(ui):
1490 1506 #Replace pull with a decorator to provide --rebase option
1491 1507 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1492 1508 entry[1].append(('', 'rebase', None,
1493 1509 _("rebase working directory to branch head")))
1494 1510 entry[1].append(('t', 'tool', '',
1495 1511 _("specify merge tool for rebase")))
1496 1512 cmdutil.summaryhooks.add('rebase', summaryhook)
1497 1513 cmdutil.unfinishedstates.append(
1498 1514 ['rebasestate', False, False, _('rebase in progress'),
1499 1515 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1500 1516 cmdutil.afterresolvedstates.append(
1501 1517 ['rebasestate', _('hg rebase --continue')])
1502 1518 # ensure rebased rev are not hidden
1503 1519 extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
@@ -1,393 +1,415
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > rebase=
4 4 > drawdag=$TESTDIR/drawdag.py
5 5 >
6 6 > [phases]
7 7 > publish=False
8 8 >
9 9 > [alias]
10 10 > tglog = log -G --template "{rev}: {desc}"
11 11 > EOF
12 12
13 13 $ rebasewithdag() {
14 14 > N=`$PYTHON -c "print($N+1)"`
15 15 > hg init repo$N && cd repo$N
16 16 > hg debugdrawdag
17 17 > hg rebase "$@" > _rebasetmp
18 18 > r=$?
19 19 > grep -v 'saved backup bundle' _rebasetmp
20 20 > [ $r -eq 0 ] && hg tglog
21 21 > cd ..
22 22 > return $r
23 23 > }
24 24
25 25 Single branching point, without merge:
26 26
27 27 $ rebasewithdag -b D -d Z <<'EOS'
28 28 > D E
29 29 > |/
30 30 > Z B C # C: branching point, E should be picked
31 31 > \|/ # B should not be picked
32 32 > A
33 33 > |
34 34 > R
35 35 > EOS
36 36 rebasing 3:d6003a550c2c "C" (C)
37 37 rebasing 5:4526cf523425 "D" (D)
38 38 rebasing 6:b296604d9846 "E" (E tip)
39 39 o 6: E
40 40 |
41 41 | o 5: D
42 42 |/
43 43 o 4: C
44 44 |
45 45 o 3: Z
46 46 |
47 47 | o 2: B
48 48 |/
49 49 o 1: A
50 50 |
51 51 o 0: R
52 52
53 53 Multiple branching points caused by selecting a single merge changeset:
54 54
55 55 $ rebasewithdag -b E -d Z <<'EOS'
56 56 > E
57 57 > /|
58 58 > B C D # B, C: multiple branching points
59 59 > | |/ # D should not be picked
60 60 > Z | /
61 61 > \|/
62 62 > A
63 63 > |
64 64 > R
65 65 > EOS
66 66 rebasing 2:c1e6b162678d "B" (B)
67 67 rebasing 3:d6003a550c2c "C" (C)
68 68 rebasing 6:5251e0cb7302 "E" (E tip)
69 69 o 6: E
70 70 |\
71 71 | o 5: C
72 72 | |
73 73 o | 4: B
74 74 |/
75 75 o 3: Z
76 76 |
77 77 | o 2: D
78 78 |/
79 79 o 1: A
80 80 |
81 81 o 0: R
82 82
83 83 Rebase should not extend the "--base" revset using "descendants":
84 84
85 85 $ rebasewithdag -b B -d Z <<'EOS'
86 86 > E
87 87 > /|
88 88 > Z B C # descendants(B) = B+E. With E, C will be included incorrectly
89 89 > \|/
90 90 > A
91 91 > |
92 92 > R
93 93 > EOS
94 94 rebasing 2:c1e6b162678d "B" (B)
95 95 rebasing 5:5251e0cb7302 "E" (E tip)
96 96 o 5: E
97 97 |\
98 98 | o 4: B
99 99 | |
100 100 | o 3: Z
101 101 | |
102 102 o | 2: C
103 103 |/
104 104 o 1: A
105 105 |
106 106 o 0: R
107 107
108 108 Rebase should not simplify the "--base" revset using "roots":
109 109
110 110 $ rebasewithdag -b B+E -d Z <<'EOS'
111 111 > E
112 112 > /|
113 113 > Z B C # roots(B+E) = B. Without E, C will be missed incorrectly
114 114 > \|/
115 115 > A
116 116 > |
117 117 > R
118 118 > EOS
119 119 rebasing 2:c1e6b162678d "B" (B)
120 120 rebasing 3:d6003a550c2c "C" (C)
121 121 rebasing 5:5251e0cb7302 "E" (E tip)
122 122 o 5: E
123 123 |\
124 124 | o 4: C
125 125 | |
126 126 o | 3: B
127 127 |/
128 128 o 2: Z
129 129 |
130 130 o 1: A
131 131 |
132 132 o 0: R
133 133
134 134 The destination is one of the two branching points of a merge:
135 135
136 136 $ rebasewithdag -b F -d Z <<'EOS'
137 137 > F
138 138 > / \
139 139 > E D
140 140 > / /
141 141 > Z C
142 142 > \ /
143 143 > B
144 144 > |
145 145 > A
146 146 > EOS
147 147 nothing to rebase
148 148 [1]
149 149
150 150 Multiple branching points caused by multiple bases (issue5420):
151 151
152 152 $ rebasewithdag -b E1+E2+C2+B1 -d Z <<'EOS'
153 153 > Z E2
154 154 > | /
155 155 > F E1 C2
156 156 > |/ /
157 157 > E C1 B2
158 158 > |/ /
159 159 > C B1
160 160 > |/
161 161 > B
162 162 > |
163 163 > A
164 164 > |
165 165 > R
166 166 > EOS
167 167 rebasing 3:a113dbaa660a "B1" (B1)
168 168 rebasing 5:06ce7b1cc8c2 "B2" (B2)
169 169 rebasing 6:0ac98cce32d3 "C1" (C1)
170 170 rebasing 8:781512f5e33d "C2" (C2)
171 171 rebasing 9:428d8c18f641 "E1" (E1)
172 172 rebasing 11:e1bf82f6b6df "E2" (E2)
173 173 o 12: E2
174 174 |
175 175 o 11: E1
176 176 |
177 177 | o 10: C2
178 178 | |
179 179 | o 9: C1
180 180 |/
181 181 | o 8: B2
182 182 | |
183 183 | o 7: B1
184 184 |/
185 185 o 6: Z
186 186 |
187 187 o 5: F
188 188 |
189 189 o 4: E
190 190 |
191 191 o 3: C
192 192 |
193 193 o 2: B
194 194 |
195 195 o 1: A
196 196 |
197 197 o 0: R
198 198
199 199 Multiple branching points with multiple merges:
200 200
201 201 $ rebasewithdag -b G+P -d Z <<'EOS'
202 202 > G H P
203 203 > |\ /| |\
204 204 > F E D M N
205 205 > \|/| /| |\
206 206 > Z C B I J K L
207 207 > \|/ |/ |/
208 208 > A A A
209 209 > EOS
210 210 rebasing 2:dc0947a82db8 "C" (C)
211 211 rebasing 8:215e7b0814e1 "D" (D)
212 212 rebasing 9:03ca77807e91 "E" (E)
213 213 rebasing 10:afc707c82df0 "F" (F)
214 214 rebasing 13:018caa673317 "G" (G)
215 215 rebasing 14:4f710fbd68cb "H" (H)
216 216 rebasing 3:08ebfeb61bac "I" (I)
217 217 rebasing 4:a0a5005cec67 "J" (J)
218 218 rebasing 5:83780307a7e8 "K" (K)
219 219 rebasing 6:e131637a1cb6 "L" (L)
220 220 rebasing 11:d6fe3d11d95d "M" (M)
221 221 rebasing 12:fa1e02269063 "N" (N)
222 222 rebasing 15:448b1a498430 "P" (P tip)
223 223 o 15: P
224 224 |\
225 225 | o 14: N
226 226 | |\
227 227 o \ \ 13: M
228 228 |\ \ \
229 229 | | | o 12: L
230 230 | | | |
231 231 | | o | 11: K
232 232 | | |/
233 233 | o / 10: J
234 234 | |/
235 235 o / 9: I
236 236 |/
237 237 | o 8: H
238 238 | |\
239 239 | | | o 7: G
240 240 | | |/|
241 241 | | | o 6: F
242 242 | | | |
243 243 | | o | 5: E
244 244 | | |/
245 245 | o | 4: D
246 246 | |\|
247 247 +---o 3: C
248 248 | |
249 249 o | 2: Z
250 250 | |
251 251 | o 1: B
252 252 |/
253 253 o 0: A
254 254
255 255 Slightly more complex merge case (mentioned in https://www.mercurial-scm.org/pipermail/mercurial-devel/2016-November/091074.html):
256 256
257 257 $ rebasewithdag -b A3+B3 -d Z <<'EOF'
258 258 > Z C1 A3 B3
259 259 > | / / \ / \
260 260 > M3 C0 A1 A2 B1 B2
261 261 > | / | | | |
262 262 > M2 M1 C1 C1 M3
263 263 > |
264 264 > M1
265 265 > |
266 266 > M0
267 267 > EOF
268 268 rebasing 4:8817fae53c94 "C0" (C0)
269 269 rebasing 6:06ca5dfe3b5b "B2" (B2)
270 270 rebasing 7:73508237b032 "C1" (C1)
271 271 rebasing 9:fdb955e2faed "A2" (A2)
272 272 rebasing 11:1b2f368c3cb5 "A3" (A3)
273 273 rebasing 10:0a33b0519128 "B1" (B1)
274 274 rebasing 12:bd6a37b5b67a "B3" (B3 tip)
275 275 o 12: B3
276 276 |\
277 277 | o 11: B1
278 278 | |
279 279 | | o 10: A3
280 280 | | |\
281 281 | +---o 9: A2
282 282 | | |
283 283 | o | 8: C1
284 284 | | |
285 285 o | | 7: B2
286 286 | | |
287 287 | o | 6: C0
288 288 |/ /
289 289 o | 5: Z
290 290 | |
291 291 o | 4: M3
292 292 | |
293 293 o | 3: M2
294 294 | |
295 295 | o 2: A1
296 296 |/
297 297 o 1: M1
298 298 |
299 299 o 0: M0
300 300
301 301 Mixed rebasable and non-rebasable bases (unresolved, issue5422):
302 302
303 303 $ rebasewithdag -b C+D -d B <<'EOS'
304 304 > D
305 305 > /
306 306 > B C
307 307 > |/
308 308 > A
309 309 > EOS
310 310 nothing to rebase
311 311 [1]
312 312
313 313 Disconnected graph:
314 314
315 315 $ rebasewithdag -b B -d Z <<'EOS'
316 316 > B
317 317 > |
318 318 > Z A
319 319 > EOS
320 320 nothing to rebase from 112478962961 to 48b9aae0607f
321 321 [1]
322 322
323 323 Multiple roots. Roots are ancestors of dest:
324 324
325 325 $ rebasewithdag -b B+D -d Z <<'EOF'
326 326 > D Z B
327 327 > \|\|
328 328 > C A
329 329 > EOF
330 330 rebasing 2:112478962961 "B" (B)
331 331 rebasing 3:b70f76719894 "D" (D)
332 332 o 4: D
333 333 |
334 334 | o 3: B
335 335 |/
336 336 o 2: Z
337 337 |\
338 338 | o 1: C
339 339 |
340 340 o 0: A
341 341
342 342 Multiple roots. One root is not an ancestor of dest:
343 343
344 344 $ rebasewithdag -b B+D -d Z <<'EOF'
345 345 > Z B D
346 346 > \|\|
347 347 > A C
348 348 > EOF
349 349 nothing to rebase from 86d01f49c0d9+b70f76719894 to 262e37e34f63
350 350 [1]
351 351
352 352 Multiple roots. One root is not an ancestor of dest. Select using a merge:
353 353
354 354 $ rebasewithdag -b E -d Z <<'EOF'
355 355 > E
356 356 > |\
357 357 > Z B D
358 358 > \|\|
359 359 > A C
360 360 > EOF
361 361 rebasing 2:86d01f49c0d9 "B" (B)
362 362 rebasing 5:539a0ff83ea9 "E" (E tip)
363 363 o 5: E
364 364 |\
365 365 | o 4: B
366 366 | |\
367 367 | | o 3: Z
368 368 | | |
369 369 o | | 2: D
370 370 |/ /
371 371 o / 1: C
372 372 /
373 373 o 0: A
374 374
375 375 Multiple roots. Two children share two parents while dest has only one parent:
376 376
377 377 $ rebasewithdag -b B+D -d Z <<'EOF'
378 378 > Z B D
379 379 > \|\|\
380 380 > A C A
381 381 > EOF
382 382 rebasing 2:86d01f49c0d9 "B" (B)
383 383 rebasing 3:b7df2ca01aa8 "D" (D)
384 384 o 4: D
385 385 |\
386 386 +---o 3: B
387 387 | |/
388 388 | o 2: Z
389 389 | |
390 390 o | 1: C
391 391 /
392 392 o 0: A
393 393
394 Require a destination
395 $ cat >> $HGRCPATH <<EOF
396 > [commands]
397 > rebase.requiredest = True
398 > EOF
399 $ hg init repo
400 $ cd repo
401 $ echo a >> a
402 $ hg commit -qAm aa
403 $ echo b >> b
404 $ hg commit -qAm bb
405 $ hg up ".^"
406 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
407 $ echo c >> c
408 $ hg commit -qAm cc
409 $ hg rebase
410 abort: you must specify a destination
411 (use: hg rebase -d REV)
412 [255]
413 $ hg rebase -d 1
414 rebasing 2:5db65b93a12b "cc" (tip)
415 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/5db65b93a12b-4fb789ec-backup.hg (glob)
General Comments 0
You need to be logged in to leave comments. Login now