##// END OF EJS Templates
rebase: avoid RevlogError when computing obsoletenotrebased (issue5907)...
Matt Harbison -
r38241:69d1cafe stable
parent child Browse files
Show More
@@ -1,1858 +1,1859
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 nullrev,
25 25 short,
26 26 )
27 27 from mercurial import (
28 28 bookmarks,
29 29 cmdutil,
30 30 commands,
31 31 copies,
32 32 destutil,
33 33 dirstateguard,
34 34 error,
35 35 extensions,
36 36 hg,
37 37 lock,
38 38 merge as mergemod,
39 39 mergeutil,
40 40 obsolete,
41 41 obsutil,
42 42 patch,
43 43 phases,
44 44 pycompat,
45 45 registrar,
46 46 repair,
47 47 revset,
48 48 revsetlang,
49 49 scmutil,
50 50 smartset,
51 51 util,
52 52 )
53 53
54 54 release = lock.release
55 55
56 56 # The following constants are used throughout the rebase module. The ordering of
57 57 # their values must be maintained.
58 58
59 59 # Indicates that a revision needs to be rebased
60 60 revtodo = -1
61 61 revtodostr = '-1'
62 62
63 63 # legacy revstates no longer needed in current code
64 64 # -2: nullmerge, -3: revignored, -4: revprecursor, -5: revpruned
65 65 legacystates = {'-2', '-3', '-4', '-5'}
66 66
67 67 cmdtable = {}
68 68 command = registrar.command(cmdtable)
69 69 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
70 70 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
71 71 # be specifying the version(s) of Mercurial they are tested with, or
72 72 # leave the attribute unspecified.
73 73 testedwith = 'ships-with-hg-core'
74 74
75 75 def _nothingtorebase():
76 76 return 1
77 77
78 78 def _savegraft(ctx, extra):
79 79 s = ctx.extra().get('source', None)
80 80 if s is not None:
81 81 extra['source'] = s
82 82 s = ctx.extra().get('intermediate-source', None)
83 83 if s is not None:
84 84 extra['intermediate-source'] = s
85 85
86 86 def _savebranch(ctx, extra):
87 87 extra['branch'] = ctx.branch()
88 88
89 89 def _destrebase(repo, sourceset, destspace=None):
90 90 """small wrapper around destmerge to pass the right extra args
91 91
92 92 Please wrap destutil.destmerge instead."""
93 93 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
94 94 onheadcheck=False, destspace=destspace)
95 95
96 96 revsetpredicate = registrar.revsetpredicate()
97 97
98 98 @revsetpredicate('_destrebase')
99 99 def _revsetdestrebase(repo, subset, x):
100 100 # ``_rebasedefaultdest()``
101 101
102 102 # default destination for rebase.
103 103 # # XXX: Currently private because I expect the signature to change.
104 104 # # XXX: - bailing out in case of ambiguity vs returning all data.
105 105 # i18n: "_rebasedefaultdest" is a keyword
106 106 sourceset = None
107 107 if x is not None:
108 108 sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
109 109 return subset & smartset.baseset([_destrebase(repo, sourceset)])
110 110
111 111 @revsetpredicate('_destautoorphanrebase')
112 112 def _revsetdestautoorphanrebase(repo, subset, x):
113 113 """automatic rebase destination for a single orphan revision"""
114 114 unfi = repo.unfiltered()
115 115 obsoleted = unfi.revs('obsolete()')
116 116
117 117 src = revset.getset(repo, subset, x).first()
118 118
119 119 # Empty src or already obsoleted - Do not return a destination
120 120 if not src or src in obsoleted:
121 121 return smartset.baseset()
122 122 dests = destutil.orphanpossibledestination(repo, src)
123 123 if len(dests) > 1:
124 124 raise error.Abort(
125 125 _("ambiguous automatic rebase: %r could end up on any of %r") % (
126 126 src, dests))
127 127 # We have zero or one destination, so we can just return here.
128 128 return smartset.baseset(dests)
129 129
130 130 def _ctxdesc(ctx):
131 131 """short description for a context"""
132 132 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
133 133 ctx.description().split('\n', 1)[0])
134 134 repo = ctx.repo()
135 135 names = []
136 136 for nsname, ns in repo.names.iteritems():
137 137 if nsname == 'branches':
138 138 continue
139 139 names.extend(ns.names(repo, ctx.node()))
140 140 if names:
141 141 desc += ' (%s)' % ' '.join(names)
142 142 return desc
143 143
144 144 class rebaseruntime(object):
145 145 """This class is a container for rebase runtime state"""
146 146 def __init__(self, repo, ui, inmemory=False, opts=None):
147 147 if opts is None:
148 148 opts = {}
149 149
150 150 # prepared: whether we have rebasestate prepared or not. Currently it
151 151 # decides whether "self.repo" is unfiltered or not.
152 152 # The rebasestate has explicit hash to hash instructions not depending
153 153 # on visibility. If rebasestate exists (in-memory or on-disk), use
154 154 # unfiltered repo to avoid visibility issues.
155 155 # Before knowing rebasestate (i.e. when starting a new rebase (not
156 156 # --continue or --abort)), the original repo should be used so
157 157 # visibility-dependent revsets are correct.
158 158 self.prepared = False
159 159 self._repo = repo
160 160
161 161 self.ui = ui
162 162 self.opts = opts
163 163 self.originalwd = None
164 164 self.external = nullrev
165 165 # Mapping between the old revision id and either what is the new rebased
166 166 # revision or what needs to be done with the old revision. The state
167 167 # dict will be what contains most of the rebase progress state.
168 168 self.state = {}
169 169 self.activebookmark = None
170 170 self.destmap = {}
171 171 self.skipped = set()
172 172
173 173 self.collapsef = opts.get('collapse', False)
174 174 self.collapsemsg = cmdutil.logmessage(ui, opts)
175 175 self.date = opts.get('date', None)
176 176
177 177 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
178 178 self.extrafns = [_savegraft]
179 179 if e:
180 180 self.extrafns = [e]
181 181
182 182 self.keepf = opts.get('keep', False)
183 183 self.keepbranchesf = opts.get('keepbranches', False)
184 184 self.obsoletenotrebased = {}
185 185 self.obsoletewithoutsuccessorindestination = set()
186 186 self.inmemory = inmemory
187 187
188 188 @property
189 189 def repo(self):
190 190 if self.prepared:
191 191 return self._repo.unfiltered()
192 192 else:
193 193 return self._repo
194 194
195 195 def storestatus(self, tr=None):
196 196 """Store the current status to allow recovery"""
197 197 if tr:
198 198 tr.addfilegenerator('rebasestate', ('rebasestate',),
199 199 self._writestatus, location='plain')
200 200 else:
201 201 with self.repo.vfs("rebasestate", "w") as f:
202 202 self._writestatus(f)
203 203
204 204 def _writestatus(self, f):
205 205 repo = self.repo
206 206 assert repo.filtername is None
207 207 f.write(repo[self.originalwd].hex() + '\n')
208 208 # was "dest". we now write dest per src root below.
209 209 f.write('\n')
210 210 f.write(repo[self.external].hex() + '\n')
211 211 f.write('%d\n' % int(self.collapsef))
212 212 f.write('%d\n' % int(self.keepf))
213 213 f.write('%d\n' % int(self.keepbranchesf))
214 214 f.write('%s\n' % (self.activebookmark or ''))
215 215 destmap = self.destmap
216 216 for d, v in self.state.iteritems():
217 217 oldrev = repo[d].hex()
218 218 if v >= 0:
219 219 newrev = repo[v].hex()
220 220 else:
221 221 newrev = "%d" % v
222 222 destnode = repo[destmap[d]].hex()
223 223 f.write("%s:%s:%s\n" % (oldrev, newrev, destnode))
224 224 repo.ui.debug('rebase status stored\n')
225 225
226 226 def restorestatus(self):
227 227 """Restore a previously stored status"""
228 228 self.prepared = True
229 229 repo = self.repo
230 230 assert repo.filtername is None
231 231 keepbranches = None
232 232 legacydest = None
233 233 collapse = False
234 234 external = nullrev
235 235 activebookmark = None
236 236 state = {}
237 237 destmap = {}
238 238
239 239 try:
240 240 f = repo.vfs("rebasestate")
241 241 for i, l in enumerate(f.read().splitlines()):
242 242 if i == 0:
243 243 originalwd = repo[l].rev()
244 244 elif i == 1:
245 245 # this line should be empty in newer version. but legacy
246 246 # clients may still use it
247 247 if l:
248 248 legacydest = repo[l].rev()
249 249 elif i == 2:
250 250 external = repo[l].rev()
251 251 elif i == 3:
252 252 collapse = bool(int(l))
253 253 elif i == 4:
254 254 keep = bool(int(l))
255 255 elif i == 5:
256 256 keepbranches = bool(int(l))
257 257 elif i == 6 and not (len(l) == 81 and ':' in l):
258 258 # line 6 is a recent addition, so for backwards
259 259 # compatibility check that the line doesn't look like the
260 260 # oldrev:newrev lines
261 261 activebookmark = l
262 262 else:
263 263 args = l.split(':')
264 264 oldrev = repo[args[0]].rev()
265 265 newrev = args[1]
266 266 if newrev in legacystates:
267 267 continue
268 268 if len(args) > 2:
269 269 destrev = repo[args[2]].rev()
270 270 else:
271 271 destrev = legacydest
272 272 destmap[oldrev] = destrev
273 273 if newrev == revtodostr:
274 274 state[oldrev] = revtodo
275 275 # Legacy compat special case
276 276 else:
277 277 state[oldrev] = repo[newrev].rev()
278 278
279 279 except IOError as err:
280 280 if err.errno != errno.ENOENT:
281 281 raise
282 282 cmdutil.wrongtooltocontinue(repo, _('rebase'))
283 283
284 284 if keepbranches is None:
285 285 raise error.Abort(_('.hg/rebasestate is incomplete'))
286 286
287 287 skipped = set()
288 288 # recompute the set of skipped revs
289 289 if not collapse:
290 290 seen = set(destmap.values())
291 291 for old, new in sorted(state.items()):
292 292 if new != revtodo and new in seen:
293 293 skipped.add(old)
294 294 seen.add(new)
295 295 repo.ui.debug('computed skipped revs: %s\n' %
296 296 (' '.join('%d' % r for r in sorted(skipped)) or ''))
297 297 repo.ui.debug('rebase status resumed\n')
298 298
299 299 self.originalwd = originalwd
300 300 self.destmap = destmap
301 301 self.state = state
302 302 self.skipped = skipped
303 303 self.collapsef = collapse
304 304 self.keepf = keep
305 305 self.keepbranchesf = keepbranches
306 306 self.external = external
307 307 self.activebookmark = activebookmark
308 308
309 309 def _handleskippingobsolete(self, obsoleterevs, destmap):
310 310 """Compute structures necessary for skipping obsolete revisions
311 311
312 312 obsoleterevs: iterable of all obsolete revisions in rebaseset
313 313 destmap: {srcrev: destrev} destination revisions
314 314 """
315 315 self.obsoletenotrebased = {}
316 316 if not self.ui.configbool('experimental', 'rebaseskipobsolete'):
317 317 return
318 318 obsoleteset = set(obsoleterevs)
319 319 (self.obsoletenotrebased,
320 320 self.obsoletewithoutsuccessorindestination,
321 321 obsoleteextinctsuccessors) = _computeobsoletenotrebased(
322 322 self.repo, obsoleteset, destmap)
323 323 skippedset = set(self.obsoletenotrebased)
324 324 skippedset.update(self.obsoletewithoutsuccessorindestination)
325 325 skippedset.update(obsoleteextinctsuccessors)
326 326 _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
327 327
328 328 def _prepareabortorcontinue(self, isabort):
329 329 try:
330 330 self.restorestatus()
331 331 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
332 332 except error.RepoLookupError:
333 333 if isabort:
334 334 clearstatus(self.repo)
335 335 clearcollapsemsg(self.repo)
336 336 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
337 337 ' only broken state is cleared)\n'))
338 338 return 0
339 339 else:
340 340 msg = _('cannot continue inconsistent rebase')
341 341 hint = _('use "hg rebase --abort" to clear broken state')
342 342 raise error.Abort(msg, hint=hint)
343 343 if isabort:
344 344 return abort(self.repo, self.originalwd, self.destmap,
345 345 self.state, activebookmark=self.activebookmark)
346 346
347 347 def _preparenewrebase(self, destmap):
348 348 if not destmap:
349 349 return _nothingtorebase()
350 350
351 351 rebaseset = destmap.keys()
352 352 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
353 353 if (not (self.keepf or allowunstable)
354 354 and self.repo.revs('first(children(%ld) - %ld)',
355 355 rebaseset, rebaseset)):
356 356 raise error.Abort(
357 357 _("can't remove original changesets with"
358 358 " unrebased descendants"),
359 359 hint=_('use --keep to keep original changesets'))
360 360
361 361 result = buildstate(self.repo, destmap, self.collapsef)
362 362
363 363 if not result:
364 364 # Empty state built, nothing to rebase
365 365 self.ui.status(_('nothing to rebase\n'))
366 366 return _nothingtorebase()
367 367
368 368 for root in self.repo.set('roots(%ld)', rebaseset):
369 369 if not self.keepf and not root.mutable():
370 370 raise error.Abort(_("can't rebase public changeset %s")
371 371 % root,
372 372 hint=_("see 'hg help phases' for details"))
373 373
374 374 (self.originalwd, self.destmap, self.state) = result
375 375 if self.collapsef:
376 376 dests = set(self.destmap.values())
377 377 if len(dests) != 1:
378 378 raise error.Abort(
379 379 _('--collapse does not work with multiple destinations'))
380 380 destrev = next(iter(dests))
381 381 destancestors = self.repo.changelog.ancestors([destrev],
382 382 inclusive=True)
383 383 self.external = externalparent(self.repo, self.state, destancestors)
384 384
385 385 for destrev in sorted(set(destmap.values())):
386 386 dest = self.repo[destrev]
387 387 if dest.closesbranch() and not self.keepbranchesf:
388 388 self.ui.status(_('reopening closed branch head %s\n') % dest)
389 389
390 390 self.prepared = True
391 391
392 392 def _assignworkingcopy(self):
393 393 if self.inmemory:
394 394 from mercurial.context import overlayworkingctx
395 395 self.wctx = overlayworkingctx(self.repo)
396 396 self.repo.ui.debug("rebasing in-memory\n")
397 397 else:
398 398 self.wctx = self.repo[None]
399 399 self.repo.ui.debug("rebasing on disk\n")
400 400 self.repo.ui.log("rebase", "", rebase_imm_used=self.inmemory)
401 401
402 402 def _performrebase(self, tr):
403 403 self._assignworkingcopy()
404 404 repo, ui = self.repo, self.ui
405 405 if self.keepbranchesf:
406 406 # insert _savebranch at the start of extrafns so if
407 407 # there's a user-provided extrafn it can clobber branch if
408 408 # desired
409 409 self.extrafns.insert(0, _savebranch)
410 410 if self.collapsef:
411 411 branches = set()
412 412 for rev in self.state:
413 413 branches.add(repo[rev].branch())
414 414 if len(branches) > 1:
415 415 raise error.Abort(_('cannot collapse multiple named '
416 416 'branches'))
417 417
418 418 # Calculate self.obsoletenotrebased
419 419 obsrevs = _filterobsoleterevs(self.repo, self.state)
420 420 self._handleskippingobsolete(obsrevs, self.destmap)
421 421
422 422 # Keep track of the active bookmarks in order to reset them later
423 423 self.activebookmark = self.activebookmark or repo._activebookmark
424 424 if self.activebookmark:
425 425 bookmarks.deactivate(repo)
426 426
427 427 # Store the state before we begin so users can run 'hg rebase --abort'
428 428 # if we fail before the transaction closes.
429 429 self.storestatus()
430 430 if tr:
431 431 # When using single transaction, store state when transaction
432 432 # commits.
433 433 self.storestatus(tr)
434 434
435 435 cands = [k for k, v in self.state.iteritems() if v == revtodo]
436 436 total = len(cands)
437 437 posholder = [0]
438 438 def progress(ctx):
439 439 posholder[0] += 1
440 440 self.repo.ui.progress(_("rebasing"), posholder[0],
441 441 ("%d:%s" % (ctx.rev(), ctx)),
442 442 _('changesets'), total)
443 443 allowdivergence = self.ui.configbool(
444 444 'experimental', 'evolution.allowdivergence')
445 445 for subset in sortsource(self.destmap):
446 446 sortedrevs = self.repo.revs('sort(%ld, -topo)', subset)
447 447 if not allowdivergence:
448 448 sortedrevs -= self.repo.revs(
449 449 'descendants(%ld) and not %ld',
450 450 self.obsoletewithoutsuccessorindestination,
451 451 self.obsoletewithoutsuccessorindestination,
452 452 )
453 453 for rev in sortedrevs:
454 454 self._rebasenode(tr, rev, allowdivergence, progress)
455 455 ui.progress(_('rebasing'), None)
456 456 ui.note(_('rebase merging completed\n'))
457 457
458 458 def _concludenode(self, rev, p1, p2, editor, commitmsg=None):
459 459 '''Commit the wd changes with parents p1 and p2.
460 460
461 461 Reuse commit info from rev but also store useful information in extra.
462 462 Return node of committed revision.'''
463 463 repo = self.repo
464 464 ctx = repo[rev]
465 465 if commitmsg is None:
466 466 commitmsg = ctx.description()
467 467 date = self.date
468 468 if date is None:
469 469 date = ctx.date()
470 470 extra = {'rebase_source': ctx.hex()}
471 471 for c in self.extrafns:
472 472 c(ctx, extra)
473 473 keepbranch = self.keepbranchesf and repo[p1].branch() != ctx.branch()
474 474 destphase = max(ctx.phase(), phases.draft)
475 475 overrides = {('phases', 'new-commit'): destphase}
476 476 if keepbranch:
477 477 overrides[('ui', 'allowemptycommit')] = True
478 478 with repo.ui.configoverride(overrides, 'rebase'):
479 479 if self.inmemory:
480 480 newnode = commitmemorynode(repo, p1, p2,
481 481 wctx=self.wctx,
482 482 extra=extra,
483 483 commitmsg=commitmsg,
484 484 editor=editor,
485 485 user=ctx.user(),
486 486 date=date)
487 487 mergemod.mergestate.clean(repo)
488 488 else:
489 489 newnode = commitnode(repo, p1, p2,
490 490 extra=extra,
491 491 commitmsg=commitmsg,
492 492 editor=editor,
493 493 user=ctx.user(),
494 494 date=date)
495 495
496 496 if newnode is None:
497 497 # If it ended up being a no-op commit, then the normal
498 498 # merge state clean-up path doesn't happen, so do it
499 499 # here. Fix issue5494
500 500 mergemod.mergestate.clean(repo)
501 501 return newnode
502 502
503 503 def _rebasenode(self, tr, rev, allowdivergence, progressfn):
504 504 repo, ui, opts = self.repo, self.ui, self.opts
505 505 dest = self.destmap[rev]
506 506 ctx = repo[rev]
507 507 desc = _ctxdesc(ctx)
508 508 if self.state[rev] == rev:
509 509 ui.status(_('already rebased %s\n') % desc)
510 510 elif (not allowdivergence
511 511 and rev in self.obsoletewithoutsuccessorindestination):
512 512 msg = _('note: not rebasing %s and its descendants as '
513 513 'this would cause divergence\n') % desc
514 514 repo.ui.status(msg)
515 515 self.skipped.add(rev)
516 516 elif rev in self.obsoletenotrebased:
517 517 succ = self.obsoletenotrebased[rev]
518 518 if succ is None:
519 519 msg = _('note: not rebasing %s, it has no '
520 520 'successor\n') % desc
521 521 else:
522 522 succdesc = _ctxdesc(repo[succ])
523 523 msg = (_('note: not rebasing %s, already in '
524 524 'destination as %s\n') % (desc, succdesc))
525 525 repo.ui.status(msg)
526 526 # Make clearrebased aware state[rev] is not a true successor
527 527 self.skipped.add(rev)
528 528 # Record rev as moved to its desired destination in self.state.
529 529 # This helps bookmark and working parent movement.
530 530 dest = max(adjustdest(repo, rev, self.destmap, self.state,
531 531 self.skipped))
532 532 self.state[rev] = dest
533 533 elif self.state[rev] == revtodo:
534 534 ui.status(_('rebasing %s\n') % desc)
535 535 progressfn(ctx)
536 536 p1, p2, base = defineparents(repo, rev, self.destmap,
537 537 self.state, self.skipped,
538 538 self.obsoletenotrebased)
539 539 if len(repo[None].parents()) == 2:
540 540 repo.ui.debug('resuming interrupted rebase\n')
541 541 else:
542 542 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
543 543 with ui.configoverride(overrides, 'rebase'):
544 544 stats = rebasenode(repo, rev, p1, base, self.collapsef,
545 545 dest, wctx=self.wctx)
546 546 if stats.unresolvedcount > 0:
547 547 if self.inmemory:
548 548 raise error.InMemoryMergeConflictsError()
549 549 else:
550 550 raise error.InterventionRequired(
551 551 _('unresolved conflicts (see hg '
552 552 'resolve, then hg rebase --continue)'))
553 553 if not self.collapsef:
554 554 merging = p2 != nullrev
555 555 editform = cmdutil.mergeeditform(merging, 'rebase')
556 556 editor = cmdutil.getcommiteditor(editform=editform,
557 557 **pycompat.strkwargs(opts))
558 558 newnode = self._concludenode(rev, p1, p2, editor)
559 559 else:
560 560 # Skip commit if we are collapsing
561 561 if self.inmemory:
562 562 self.wctx.setbase(repo[p1])
563 563 else:
564 564 repo.setparents(repo[p1].node())
565 565 newnode = None
566 566 # Update the state
567 567 if newnode is not None:
568 568 self.state[rev] = repo[newnode].rev()
569 569 ui.debug('rebased as %s\n' % short(newnode))
570 570 else:
571 571 if not self.collapsef:
572 572 ui.warn(_('note: rebase of %d:%s created no changes '
573 573 'to commit\n') % (rev, ctx))
574 574 self.skipped.add(rev)
575 575 self.state[rev] = p1
576 576 ui.debug('next revision set to %d\n' % p1)
577 577 else:
578 578 ui.status(_('already rebased %s as %s\n') %
579 579 (desc, repo[self.state[rev]]))
580 580 if not tr:
581 581 # When not using single transaction, store state after each
582 582 # commit is completely done. On InterventionRequired, we thus
583 583 # won't store the status. Instead, we'll hit the "len(parents) == 2"
584 584 # case and realize that the commit was in progress.
585 585 self.storestatus()
586 586
587 587 def _finishrebase(self):
588 588 repo, ui, opts = self.repo, self.ui, self.opts
589 589 fm = ui.formatter('rebase', opts)
590 590 fm.startitem()
591 591 if self.collapsef:
592 592 p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
593 593 self.state, self.skipped,
594 594 self.obsoletenotrebased)
595 595 editopt = opts.get('edit')
596 596 editform = 'rebase.collapse'
597 597 if self.collapsemsg:
598 598 commitmsg = self.collapsemsg
599 599 else:
600 600 commitmsg = 'Collapsed revision'
601 601 for rebased in sorted(self.state):
602 602 if rebased not in self.skipped:
603 603 commitmsg += '\n* %s' % repo[rebased].description()
604 604 editopt = True
605 605 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
606 606 revtoreuse = max(self.state)
607 607
608 608 newnode = self._concludenode(revtoreuse, p1, self.external,
609 609 editor, commitmsg=commitmsg)
610 610
611 611 if newnode is not None:
612 612 newrev = repo[newnode].rev()
613 613 for oldrev in self.state:
614 614 self.state[oldrev] = newrev
615 615
616 616 if 'qtip' in repo.tags():
617 617 updatemq(repo, self.state, self.skipped,
618 618 **pycompat.strkwargs(opts))
619 619
620 620 # restore original working directory
621 621 # (we do this before stripping)
622 622 newwd = self.state.get(self.originalwd, self.originalwd)
623 623 if newwd < 0:
624 624 # original directory is a parent of rebase set root or ignored
625 625 newwd = self.originalwd
626 626 if newwd not in [c.rev() for c in repo[None].parents()]:
627 627 ui.note(_("update back to initial working directory parent\n"))
628 628 hg.updaterepo(repo, newwd, False)
629 629
630 630 collapsedas = None
631 631 if self.collapsef and not self.keepf:
632 632 collapsedas = newnode
633 633 clearrebased(ui, repo, self.destmap, self.state, self.skipped,
634 634 collapsedas, self.keepf, fm=fm)
635 635
636 636 clearstatus(repo)
637 637 clearcollapsemsg(repo)
638 638
639 639 ui.note(_("rebase completed\n"))
640 640 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
641 641 if self.skipped:
642 642 skippedlen = len(self.skipped)
643 643 ui.note(_("%d revisions have been skipped\n") % skippedlen)
644 644 fm.end()
645 645
646 646 if (self.activebookmark and self.activebookmark in repo._bookmarks and
647 647 repo['.'].node() == repo._bookmarks[self.activebookmark]):
648 648 bookmarks.activate(repo, self.activebookmark)
649 649
650 650 @command('rebase',
651 651 [('s', 'source', '',
652 652 _('rebase the specified changeset and descendants'), _('REV')),
653 653 ('b', 'base', '',
654 654 _('rebase everything from branching point of specified changeset'),
655 655 _('REV')),
656 656 ('r', 'rev', [],
657 657 _('rebase these revisions'),
658 658 _('REV')),
659 659 ('d', 'dest', '',
660 660 _('rebase onto the specified changeset'), _('REV')),
661 661 ('', 'collapse', False, _('collapse the rebased changesets')),
662 662 ('m', 'message', '',
663 663 _('use text as collapse commit message'), _('TEXT')),
664 664 ('e', 'edit', False, _('invoke editor on commit messages')),
665 665 ('l', 'logfile', '',
666 666 _('read collapse commit message from file'), _('FILE')),
667 667 ('k', 'keep', False, _('keep original changesets')),
668 668 ('', 'keepbranches', False, _('keep original branch names')),
669 669 ('D', 'detach', False, _('(DEPRECATED)')),
670 670 ('i', 'interactive', False, _('(DEPRECATED)')),
671 671 ('t', 'tool', '', _('specify merge tool')),
672 672 ('c', 'continue', False, _('continue an interrupted rebase')),
673 673 ('a', 'abort', False, _('abort an interrupted rebase')),
674 674 ('', 'auto-orphans', '', _('automatically rebase orphan revisions '
675 675 'in the specified revset (EXPERIMENTAL)')),
676 676 ] +
677 677 cmdutil.formatteropts,
678 678 _('[-s REV | -b REV] [-d REV] [OPTION]'))
679 679 def rebase(ui, repo, **opts):
680 680 """move changeset (and descendants) to a different branch
681 681
682 682 Rebase uses repeated merging to graft changesets from one part of
683 683 history (the source) onto another (the destination). This can be
684 684 useful for linearizing *local* changes relative to a master
685 685 development tree.
686 686
687 687 Published commits cannot be rebased (see :hg:`help phases`).
688 688 To copy commits, see :hg:`help graft`.
689 689
690 690 If you don't specify a destination changeset (``-d/--dest``), rebase
691 691 will use the same logic as :hg:`merge` to pick a destination. if
692 692 the current branch contains exactly one other head, the other head
693 693 is merged with by default. Otherwise, an explicit revision with
694 694 which to merge with must be provided. (destination changeset is not
695 695 modified by rebasing, but new changesets are added as its
696 696 descendants.)
697 697
698 698 Here are the ways to select changesets:
699 699
700 700 1. Explicitly select them using ``--rev``.
701 701
702 702 2. Use ``--source`` to select a root changeset and include all of its
703 703 descendants.
704 704
705 705 3. Use ``--base`` to select a changeset; rebase will find ancestors
706 706 and their descendants which are not also ancestors of the destination.
707 707
708 708 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
709 709 rebase will use ``--base .`` as above.
710 710
711 711 If ``--source`` or ``--rev`` is used, special names ``SRC`` and ``ALLSRC``
712 712 can be used in ``--dest``. Destination would be calculated per source
713 713 revision with ``SRC`` substituted by that single source revision and
714 714 ``ALLSRC`` substituted by all source revisions.
715 715
716 716 Rebase will destroy original changesets unless you use ``--keep``.
717 717 It will also move your bookmarks (even if you do).
718 718
719 719 Some changesets may be dropped if they do not contribute changes
720 720 (e.g. merges from the destination branch).
721 721
722 722 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
723 723 a named branch with two heads. You will need to explicitly specify source
724 724 and/or destination.
725 725
726 726 If you need to use a tool to automate merge/conflict decisions, you
727 727 can specify one with ``--tool``, see :hg:`help merge-tools`.
728 728 As a caveat: the tool will not be used to mediate when a file was
729 729 deleted, there is no hook presently available for this.
730 730
731 731 If a rebase is interrupted to manually resolve a conflict, it can be
732 732 continued with --continue/-c or aborted with --abort/-a.
733 733
734 734 .. container:: verbose
735 735
736 736 Examples:
737 737
738 738 - move "local changes" (current commit back to branching point)
739 739 to the current branch tip after a pull::
740 740
741 741 hg rebase
742 742
743 743 - move a single changeset to the stable branch::
744 744
745 745 hg rebase -r 5f493448 -d stable
746 746
747 747 - splice a commit and all its descendants onto another part of history::
748 748
749 749 hg rebase --source c0c3 --dest 4cf9
750 750
751 751 - rebase everything on a branch marked by a bookmark onto the
752 752 default branch::
753 753
754 754 hg rebase --base myfeature --dest default
755 755
756 756 - collapse a sequence of changes into a single commit::
757 757
758 758 hg rebase --collapse -r 1520:1525 -d .
759 759
760 760 - move a named branch while preserving its name::
761 761
762 762 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
763 763
764 764 - stabilize orphaned changesets so history looks linear::
765 765
766 766 hg rebase -r 'orphan()-obsolete()'\
767 767 -d 'first(max((successors(max(roots(ALLSRC) & ::SRC)^)-obsolete())::) +\
768 768 max(::((roots(ALLSRC) & ::SRC)^)-obsolete()))'
769 769
770 770 Configuration Options:
771 771
772 772 You can make rebase require a destination if you set the following config
773 773 option::
774 774
775 775 [commands]
776 776 rebase.requiredest = True
777 777
778 778 By default, rebase will close the transaction after each commit. For
779 779 performance purposes, you can configure rebase to use a single transaction
780 780 across the entire rebase. WARNING: This setting introduces a significant
781 781 risk of losing the work you've done in a rebase if the rebase aborts
782 782 unexpectedly::
783 783
784 784 [rebase]
785 785 singletransaction = True
786 786
787 787 By default, rebase writes to the working copy, but you can configure it to
788 788 run in-memory for for better performance, and to allow it to run if the
789 789 working copy is dirty::
790 790
791 791 [rebase]
792 792 experimental.inmemory = True
793 793
794 794 Return Values:
795 795
796 796 Returns 0 on success, 1 if nothing to rebase or there are
797 797 unresolved conflicts.
798 798
799 799 """
800 800 inmemory = ui.configbool('rebase', 'experimental.inmemory')
801 801 if (opts.get('continue') or opts.get('abort') or
802 802 repo.currenttransaction() is not None):
803 803 # in-memory rebase is not compatible with resuming rebases.
804 804 # (Or if it is run within a transaction, since the restart logic can
805 805 # fail the entire transaction.)
806 806 inmemory = False
807 807
808 808 if opts.get('auto_orphans'):
809 809 for key in opts:
810 810 if key != 'auto_orphans' and opts.get(key):
811 811 raise error.Abort(_('--auto-orphans is incompatible with %s') %
812 812 ('--' + key))
813 813 userrevs = list(repo.revs(opts.get('auto_orphans')))
814 814 opts['rev'] = [revsetlang.formatspec('%ld and orphan()', userrevs)]
815 815 opts['dest'] = '_destautoorphanrebase(SRC)'
816 816
817 817 if inmemory:
818 818 try:
819 819 # in-memory merge doesn't support conflicts, so if we hit any, abort
820 820 # and re-run as an on-disk merge.
821 821 overrides = {('rebase', 'singletransaction'): True}
822 822 with ui.configoverride(overrides, 'rebase'):
823 823 return _origrebase(ui, repo, inmemory=inmemory, **opts)
824 824 except error.InMemoryMergeConflictsError:
825 825 ui.warn(_('hit merge conflicts; re-running rebase without in-memory'
826 826 ' merge\n'))
827 827 _origrebase(ui, repo, **{'abort': True})
828 828 return _origrebase(ui, repo, inmemory=False, **opts)
829 829 else:
830 830 return _origrebase(ui, repo, **opts)
831 831
832 832 def _origrebase(ui, repo, inmemory=False, **opts):
833 833 opts = pycompat.byteskwargs(opts)
834 834 rbsrt = rebaseruntime(repo, ui, inmemory, opts)
835 835
836 836 with repo.wlock(), repo.lock():
837 837 # Validate input and define rebasing points
838 838 destf = opts.get('dest', None)
839 839 srcf = opts.get('source', None)
840 840 basef = opts.get('base', None)
841 841 revf = opts.get('rev', [])
842 842 # search default destination in this space
843 843 # used in the 'hg pull --rebase' case, see issue 5214.
844 844 destspace = opts.get('_destspace')
845 845 contf = opts.get('continue')
846 846 abortf = opts.get('abort')
847 847 if opts.get('interactive'):
848 848 try:
849 849 if extensions.find('histedit'):
850 850 enablehistedit = ''
851 851 except KeyError:
852 852 enablehistedit = " --config extensions.histedit="
853 853 help = "hg%s help -e histedit" % enablehistedit
854 854 msg = _("interactive history editing is supported by the "
855 855 "'histedit' extension (see \"%s\")") % help
856 856 raise error.Abort(msg)
857 857
858 858 if rbsrt.collapsemsg and not rbsrt.collapsef:
859 859 raise error.Abort(
860 860 _('message can only be specified with collapse'))
861 861
862 862 if contf or abortf:
863 863 if contf and abortf:
864 864 raise error.Abort(_('cannot use both abort and continue'))
865 865 if rbsrt.collapsef:
866 866 raise error.Abort(
867 867 _('cannot use collapse with continue or abort'))
868 868 if srcf or basef or destf:
869 869 raise error.Abort(
870 870 _('abort and continue do not allow specifying revisions'))
871 871 if abortf and opts.get('tool', False):
872 872 ui.warn(_('tool option will be ignored\n'))
873 873 if contf:
874 874 ms = mergemod.mergestate.read(repo)
875 875 mergeutil.checkunresolved(ms)
876 876
877 877 retcode = rbsrt._prepareabortorcontinue(abortf)
878 878 if retcode is not None:
879 879 return retcode
880 880 else:
881 881 destmap = _definedestmap(ui, repo, inmemory, destf, srcf, basef,
882 882 revf, destspace=destspace)
883 883 retcode = rbsrt._preparenewrebase(destmap)
884 884 if retcode is not None:
885 885 return retcode
886 886 storecollapsemsg(repo, rbsrt.collapsemsg)
887 887
888 888 tr = None
889 889
890 890 singletr = ui.configbool('rebase', 'singletransaction')
891 891 if singletr:
892 892 tr = repo.transaction('rebase')
893 893
894 894 # If `rebase.singletransaction` is enabled, wrap the entire operation in
895 895 # one transaction here. Otherwise, transactions are obtained when
896 896 # committing each node, which is slower but allows partial success.
897 897 with util.acceptintervention(tr):
898 898 # Same logic for the dirstate guard, except we don't create one when
899 899 # rebasing in-memory (it's not needed).
900 900 dsguard = None
901 901 if singletr and not inmemory:
902 902 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
903 903 with util.acceptintervention(dsguard):
904 904 rbsrt._performrebase(tr)
905 905 rbsrt._finishrebase()
906 906
907 907 def _definedestmap(ui, repo, inmemory, destf=None, srcf=None, basef=None,
908 908 revf=None, destspace=None):
909 909 """use revisions argument to define destmap {srcrev: destrev}"""
910 910 if revf is None:
911 911 revf = []
912 912
913 913 # destspace is here to work around issues with `hg pull --rebase` see
914 914 # issue5214 for details
915 915 if srcf and basef:
916 916 raise error.Abort(_('cannot specify both a source and a base'))
917 917 if revf and basef:
918 918 raise error.Abort(_('cannot specify both a revision and a base'))
919 919 if revf and srcf:
920 920 raise error.Abort(_('cannot specify both a revision and a source'))
921 921
922 922 if not inmemory:
923 923 cmdutil.checkunfinished(repo)
924 924 cmdutil.bailifchanged(repo)
925 925
926 926 if ui.configbool('commands', 'rebase.requiredest') and not destf:
927 927 raise error.Abort(_('you must specify a destination'),
928 928 hint=_('use: hg rebase -d REV'))
929 929
930 930 dest = None
931 931
932 932 if revf:
933 933 rebaseset = scmutil.revrange(repo, revf)
934 934 if not rebaseset:
935 935 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
936 936 return None
937 937 elif srcf:
938 938 src = scmutil.revrange(repo, [srcf])
939 939 if not src:
940 940 ui.status(_('empty "source" revision set - nothing to rebase\n'))
941 941 return None
942 942 rebaseset = repo.revs('(%ld)::', src)
943 943 assert rebaseset
944 944 else:
945 945 base = scmutil.revrange(repo, [basef or '.'])
946 946 if not base:
947 947 ui.status(_('empty "base" revision set - '
948 948 "can't compute rebase set\n"))
949 949 return None
950 950 if destf:
951 951 # --base does not support multiple destinations
952 952 dest = scmutil.revsingle(repo, destf)
953 953 else:
954 954 dest = repo[_destrebase(repo, base, destspace=destspace)]
955 955 destf = bytes(dest)
956 956
957 957 roots = [] # selected children of branching points
958 958 bpbase = {} # {branchingpoint: [origbase]}
959 959 for b in base: # group bases by branching points
960 960 bp = repo.revs('ancestor(%d, %d)', b, dest.rev()).first()
961 961 bpbase[bp] = bpbase.get(bp, []) + [b]
962 962 if None in bpbase:
963 963 # emulate the old behavior, showing "nothing to rebase" (a better
964 964 # behavior may be abort with "cannot find branching point" error)
965 965 bpbase.clear()
966 966 for bp, bs in bpbase.iteritems(): # calculate roots
967 967 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
968 968
969 969 rebaseset = repo.revs('%ld::', roots)
970 970
971 971 if not rebaseset:
972 972 # transform to list because smartsets are not comparable to
973 973 # lists. This should be improved to honor laziness of
974 974 # smartset.
975 975 if list(base) == [dest.rev()]:
976 976 if basef:
977 977 ui.status(_('nothing to rebase - %s is both "base"'
978 978 ' and destination\n') % dest)
979 979 else:
980 980 ui.status(_('nothing to rebase - working directory '
981 981 'parent is also destination\n'))
982 982 elif not repo.revs('%ld - ::%d', base, dest.rev()):
983 983 if basef:
984 984 ui.status(_('nothing to rebase - "base" %s is '
985 985 'already an ancestor of destination '
986 986 '%s\n') %
987 987 ('+'.join(bytes(repo[r]) for r in base),
988 988 dest))
989 989 else:
990 990 ui.status(_('nothing to rebase - working '
991 991 'directory parent is already an '
992 992 'ancestor of destination %s\n') % dest)
993 993 else: # can it happen?
994 994 ui.status(_('nothing to rebase from %s to %s\n') %
995 995 ('+'.join(bytes(repo[r]) for r in base), dest))
996 996 return None
997 997
998 998 rebasingwcp = repo['.'].rev() in rebaseset
999 999 ui.log("rebase", "", rebase_rebasing_wcp=rebasingwcp)
1000 1000 if inmemory and rebasingwcp:
1001 1001 # Check these since we did not before.
1002 1002 cmdutil.checkunfinished(repo)
1003 1003 cmdutil.bailifchanged(repo)
1004 1004
1005 1005 if not destf:
1006 1006 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
1007 1007 destf = bytes(dest)
1008 1008
1009 1009 allsrc = revsetlang.formatspec('%ld', rebaseset)
1010 1010 alias = {'ALLSRC': allsrc}
1011 1011
1012 1012 if dest is None:
1013 1013 try:
1014 1014 # fast path: try to resolve dest without SRC alias
1015 1015 dest = scmutil.revsingle(repo, destf, localalias=alias)
1016 1016 except error.RepoLookupError:
1017 1017 # multi-dest path: resolve dest for each SRC separately
1018 1018 destmap = {}
1019 1019 for r in rebaseset:
1020 1020 alias['SRC'] = revsetlang.formatspec('%d', r)
1021 1021 # use repo.anyrevs instead of scmutil.revsingle because we
1022 1022 # don't want to abort if destset is empty.
1023 1023 destset = repo.anyrevs([destf], user=True, localalias=alias)
1024 1024 size = len(destset)
1025 1025 if size == 1:
1026 1026 destmap[r] = destset.first()
1027 1027 elif size == 0:
1028 1028 ui.note(_('skipping %s - empty destination\n') % repo[r])
1029 1029 else:
1030 1030 raise error.Abort(_('rebase destination for %s is not '
1031 1031 'unique') % repo[r])
1032 1032
1033 1033 if dest is not None:
1034 1034 # single-dest case: assign dest to each rev in rebaseset
1035 1035 destrev = dest.rev()
1036 1036 destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
1037 1037
1038 1038 if not destmap:
1039 1039 ui.status(_('nothing to rebase - empty destination\n'))
1040 1040 return None
1041 1041
1042 1042 return destmap
1043 1043
1044 1044 def externalparent(repo, state, destancestors):
1045 1045 """Return the revision that should be used as the second parent
1046 1046 when the revisions in state is collapsed on top of destancestors.
1047 1047 Abort if there is more than one parent.
1048 1048 """
1049 1049 parents = set()
1050 1050 source = min(state)
1051 1051 for rev in state:
1052 1052 if rev == source:
1053 1053 continue
1054 1054 for p in repo[rev].parents():
1055 1055 if (p.rev() not in state
1056 1056 and p.rev() not in destancestors):
1057 1057 parents.add(p.rev())
1058 1058 if not parents:
1059 1059 return nullrev
1060 1060 if len(parents) == 1:
1061 1061 return parents.pop()
1062 1062 raise error.Abort(_('unable to collapse on top of %d, there is more '
1063 1063 'than one external parent: %s') %
1064 1064 (max(destancestors),
1065 1065 ', '.join("%d" % p for p in sorted(parents))))
1066 1066
1067 1067 def commitmemorynode(repo, p1, p2, wctx, editor, extra, user, date, commitmsg):
1068 1068 '''Commit the memory changes with parents p1 and p2.
1069 1069 Return node of committed revision.'''
1070 1070 # Replicates the empty check in ``repo.commit``.
1071 1071 if wctx.isempty() and not repo.ui.configbool('ui', 'allowemptycommit'):
1072 1072 return None
1073 1073
1074 1074 # By convention, ``extra['branch']`` (set by extrafn) clobbers
1075 1075 # ``branch`` (used when passing ``--keepbranches``).
1076 1076 branch = repo[p1].branch()
1077 1077 if 'branch' in extra:
1078 1078 branch = extra['branch']
1079 1079
1080 1080 memctx = wctx.tomemctx(commitmsg, parents=(p1, p2), date=date,
1081 1081 extra=extra, user=user, branch=branch, editor=editor)
1082 1082 commitres = repo.commitctx(memctx)
1083 1083 wctx.clean() # Might be reused
1084 1084 return commitres
1085 1085
1086 1086 def commitnode(repo, p1, p2, editor, extra, user, date, commitmsg):
1087 1087 '''Commit the wd changes with parents p1 and p2.
1088 1088 Return node of committed revision.'''
1089 1089 dsguard = util.nullcontextmanager()
1090 1090 if not repo.ui.configbool('rebase', 'singletransaction'):
1091 1091 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
1092 1092 with dsguard:
1093 1093 repo.setparents(repo[p1].node(), repo[p2].node())
1094 1094
1095 1095 # Commit might fail if unresolved files exist
1096 1096 newnode = repo.commit(text=commitmsg, user=user, date=date,
1097 1097 extra=extra, editor=editor)
1098 1098
1099 1099 repo.dirstate.setbranch(repo[newnode].branch())
1100 1100 return newnode
1101 1101
1102 1102 def rebasenode(repo, rev, p1, base, collapse, dest, wctx):
1103 1103 'Rebase a single revision rev on top of p1 using base as merge ancestor'
1104 1104 # Merge phase
1105 1105 # Update to destination and merge it with local
1106 1106 if wctx.isinmemory():
1107 1107 wctx.setbase(repo[p1])
1108 1108 else:
1109 1109 if repo['.'].rev() != p1:
1110 1110 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
1111 1111 mergemod.update(repo, p1, False, True)
1112 1112 else:
1113 1113 repo.ui.debug(" already in destination\n")
1114 1114 # This is, alas, necessary to invalidate workingctx's manifest cache,
1115 1115 # as well as other data we litter on it in other places.
1116 1116 wctx = repo[None]
1117 1117 repo.dirstate.write(repo.currenttransaction())
1118 1118 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
1119 1119 if base is not None:
1120 1120 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
1121 1121 # When collapsing in-place, the parent is the common ancestor, we
1122 1122 # have to allow merging with it.
1123 1123 stats = mergemod.update(repo, rev, True, True, base, collapse,
1124 1124 labels=['dest', 'source'], wc=wctx)
1125 1125 if collapse:
1126 1126 copies.duplicatecopies(repo, wctx, rev, dest)
1127 1127 else:
1128 1128 # If we're not using --collapse, we need to
1129 1129 # duplicate copies between the revision we're
1130 1130 # rebasing and its first parent, but *not*
1131 1131 # duplicate any copies that have already been
1132 1132 # performed in the destination.
1133 1133 p1rev = repo[rev].p1().rev()
1134 1134 copies.duplicatecopies(repo, wctx, rev, p1rev, skiprev=dest)
1135 1135 return stats
1136 1136
1137 1137 def adjustdest(repo, rev, destmap, state, skipped):
1138 1138 """adjust rebase destination given the current rebase state
1139 1139
1140 1140 rev is what is being rebased. Return a list of two revs, which are the
1141 1141 adjusted destinations for rev's p1 and p2, respectively. If a parent is
1142 1142 nullrev, return dest without adjustment for it.
1143 1143
1144 1144 For example, when doing rebasing B+E to F, C to G, rebase will first move B
1145 1145 to B1, and E's destination will be adjusted from F to B1.
1146 1146
1147 1147 B1 <- written during rebasing B
1148 1148 |
1149 1149 F <- original destination of B, E
1150 1150 |
1151 1151 | E <- rev, which is being rebased
1152 1152 | |
1153 1153 | D <- prev, one parent of rev being checked
1154 1154 | |
1155 1155 | x <- skipped, ex. no successor or successor in (::dest)
1156 1156 | |
1157 1157 | C <- rebased as C', different destination
1158 1158 | |
1159 1159 | B <- rebased as B1 C'
1160 1160 |/ |
1161 1161 A G <- destination of C, different
1162 1162
1163 1163 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
1164 1164 first move C to C1, G to G1, and when it's checking H, the adjusted
1165 1165 destinations will be [C1, G1].
1166 1166
1167 1167 H C1 G1
1168 1168 /| | /
1169 1169 F G |/
1170 1170 K | | -> K
1171 1171 | C D |
1172 1172 | |/ |
1173 1173 | B | ...
1174 1174 |/ |/
1175 1175 A A
1176 1176
1177 1177 Besides, adjust dest according to existing rebase information. For example,
1178 1178
1179 1179 B C D B needs to be rebased on top of C, C needs to be rebased on top
1180 1180 \|/ of D. We will rebase C first.
1181 1181 A
1182 1182
1183 1183 C' After rebasing C, when considering B's destination, use C'
1184 1184 | instead of the original C.
1185 1185 B D
1186 1186 \ /
1187 1187 A
1188 1188 """
1189 1189 # pick already rebased revs with same dest from state as interesting source
1190 1190 dest = destmap[rev]
1191 1191 source = [s for s, d in state.items()
1192 1192 if d > 0 and destmap[s] == dest and s not in skipped]
1193 1193
1194 1194 result = []
1195 1195 for prev in repo.changelog.parentrevs(rev):
1196 1196 adjusted = dest
1197 1197 if prev != nullrev:
1198 1198 candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
1199 1199 if candidate is not None:
1200 1200 adjusted = state[candidate]
1201 1201 if adjusted == dest and dest in state:
1202 1202 adjusted = state[dest]
1203 1203 if adjusted == revtodo:
1204 1204 # sortsource should produce an order that makes this impossible
1205 1205 raise error.ProgrammingError(
1206 1206 'rev %d should be rebased already at this time' % dest)
1207 1207 result.append(adjusted)
1208 1208 return result
1209 1209
1210 1210 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
1211 1211 """
1212 1212 Abort if rebase will create divergence or rebase is noop because of markers
1213 1213
1214 1214 `rebaseobsrevs`: set of obsolete revision in source
1215 1215 `rebaseobsskipped`: set of revisions from source skipped because they have
1216 1216 successors in destination or no non-obsolete successor.
1217 1217 """
1218 1218 # Obsolete node with successors not in dest leads to divergence
1219 1219 divergenceok = ui.configbool('experimental',
1220 1220 'evolution.allowdivergence')
1221 1221 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
1222 1222
1223 1223 if divergencebasecandidates and not divergenceok:
1224 1224 divhashes = (bytes(repo[r])
1225 1225 for r in divergencebasecandidates)
1226 1226 msg = _("this rebase will cause "
1227 1227 "divergences from: %s")
1228 1228 h = _("to force the rebase please set "
1229 1229 "experimental.evolution.allowdivergence=True")
1230 1230 raise error.Abort(msg % (",".join(divhashes),), hint=h)
1231 1231
1232 1232 def successorrevs(unfi, rev):
1233 1233 """yield revision numbers for successors of rev"""
1234 1234 assert unfi.filtername is None
1235 1235 nodemap = unfi.changelog.nodemap
1236 1236 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
1237 1237 if s in nodemap:
1238 1238 yield nodemap[s]
1239 1239
1240 1240 def defineparents(repo, rev, destmap, state, skipped, obsskipped):
1241 1241 """Return new parents and optionally a merge base for rev being rebased
1242 1242
1243 1243 The destination specified by "dest" cannot always be used directly because
1244 1244 previously rebase result could affect destination. For example,
1245 1245
1246 1246 D E rebase -r C+D+E -d B
1247 1247 |/ C will be rebased to C'
1248 1248 B C D's new destination will be C' instead of B
1249 1249 |/ E's new destination will be C' instead of B
1250 1250 A
1251 1251
1252 1252 The new parents of a merge is slightly more complicated. See the comment
1253 1253 block below.
1254 1254 """
1255 1255 # use unfiltered changelog since successorrevs may return filtered nodes
1256 1256 assert repo.filtername is None
1257 1257 cl = repo.changelog
1258 1258 def isancestor(a, b):
1259 1259 # take revision numbers instead of nodes
1260 1260 if a == b:
1261 1261 return True
1262 1262 elif a > b:
1263 1263 return False
1264 1264 return cl.isancestor(cl.node(a), cl.node(b))
1265 1265
1266 1266 dest = destmap[rev]
1267 1267 oldps = repo.changelog.parentrevs(rev) # old parents
1268 1268 newps = [nullrev, nullrev] # new parents
1269 1269 dests = adjustdest(repo, rev, destmap, state, skipped)
1270 1270 bases = list(oldps) # merge base candidates, initially just old parents
1271 1271
1272 1272 if all(r == nullrev for r in oldps[1:]):
1273 1273 # For non-merge changeset, just move p to adjusted dest as requested.
1274 1274 newps[0] = dests[0]
1275 1275 else:
1276 1276 # For merge changeset, if we move p to dests[i] unconditionally, both
1277 1277 # parents may change and the end result looks like "the merge loses a
1278 1278 # parent", which is a surprise. This is a limit because "--dest" only
1279 1279 # accepts one dest per src.
1280 1280 #
1281 1281 # Therefore, only move p with reasonable conditions (in this order):
1282 1282 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1283 1283 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1284 1284 #
1285 1285 # Comparing with adjustdest, the logic here does some additional work:
1286 1286 # 1. decide which parents will not be moved towards dest
1287 1287 # 2. if the above decision is "no", should a parent still be moved
1288 1288 # because it was rebased?
1289 1289 #
1290 1290 # For example:
1291 1291 #
1292 1292 # C # "rebase -r C -d D" is an error since none of the parents
1293 1293 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1294 1294 # A B D # B (using rule "2."), since B will be rebased.
1295 1295 #
1296 1296 # The loop tries to be not rely on the fact that a Mercurial node has
1297 1297 # at most 2 parents.
1298 1298 for i, p in enumerate(oldps):
1299 1299 np = p # new parent
1300 1300 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1301 1301 np = dests[i]
1302 1302 elif p in state and state[p] > 0:
1303 1303 np = state[p]
1304 1304
1305 1305 # "bases" only record "special" merge bases that cannot be
1306 1306 # calculated from changelog DAG (i.e. isancestor(p, np) is False).
1307 1307 # For example:
1308 1308 #
1309 1309 # B' # rebase -s B -d D, when B was rebased to B'. dest for C
1310 1310 # | C # is B', but merge base for C is B, instead of
1311 1311 # D | # changelog.ancestor(C, B') == A. If changelog DAG and
1312 1312 # | B # "state" edges are merged (so there will be an edge from
1313 1313 # |/ # B to B'), the merge base is still ancestor(C, B') in
1314 1314 # A # the merged graph.
1315 1315 #
1316 1316 # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
1317 1317 # which uses "virtual null merge" to explain this situation.
1318 1318 if isancestor(p, np):
1319 1319 bases[i] = nullrev
1320 1320
1321 1321 # If one parent becomes an ancestor of the other, drop the ancestor
1322 1322 for j, x in enumerate(newps[:i]):
1323 1323 if x == nullrev:
1324 1324 continue
1325 1325 if isancestor(np, x): # CASE-1
1326 1326 np = nullrev
1327 1327 elif isancestor(x, np): # CASE-2
1328 1328 newps[j] = np
1329 1329 np = nullrev
1330 1330 # New parents forming an ancestor relationship does not
1331 1331 # mean the old parents have a similar relationship. Do not
1332 1332 # set bases[x] to nullrev.
1333 1333 bases[j], bases[i] = bases[i], bases[j]
1334 1334
1335 1335 newps[i] = np
1336 1336
1337 1337 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1338 1338 # base. If only p2 changes, merging using unchanged p1 as merge base is
1339 1339 # suboptimal. Therefore swap parents to make the merge sane.
1340 1340 if newps[1] != nullrev and oldps[0] == newps[0]:
1341 1341 assert len(newps) == 2 and len(oldps) == 2
1342 1342 newps.reverse()
1343 1343 bases.reverse()
1344 1344
1345 1345 # No parent change might be an error because we fail to make rev a
1346 1346 # descendent of requested dest. This can happen, for example:
1347 1347 #
1348 1348 # C # rebase -r C -d D
1349 1349 # /| # None of A and B will be changed to D and rebase fails.
1350 1350 # A B D
1351 1351 if set(newps) == set(oldps) and dest not in newps:
1352 1352 raise error.Abort(_('cannot rebase %d:%s without '
1353 1353 'moving at least one of its parents')
1354 1354 % (rev, repo[rev]))
1355 1355
1356 1356 # Source should not be ancestor of dest. The check here guarantees it's
1357 1357 # impossible. With multi-dest, the initial check does not cover complex
1358 1358 # cases since we don't have abstractions to dry-run rebase cheaply.
1359 1359 if any(p != nullrev and isancestor(rev, p) for p in newps):
1360 1360 raise error.Abort(_('source is ancestor of destination'))
1361 1361
1362 1362 # "rebasenode" updates to new p1, use the corresponding merge base.
1363 1363 if bases[0] != nullrev:
1364 1364 base = bases[0]
1365 1365 else:
1366 1366 base = None
1367 1367
1368 1368 # Check if the merge will contain unwanted changes. That may happen if
1369 1369 # there are multiple special (non-changelog ancestor) merge bases, which
1370 1370 # cannot be handled well by the 3-way merge algorithm. For example:
1371 1371 #
1372 1372 # F
1373 1373 # /|
1374 1374 # D E # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
1375 1375 # | | # as merge base, the difference between D and F will include
1376 1376 # B C # C, so the rebased F will contain C surprisingly. If "E" was
1377 1377 # |/ # chosen, the rebased F will contain B.
1378 1378 # A Z
1379 1379 #
1380 1380 # But our merge base candidates (D and E in above case) could still be
1381 1381 # better than the default (ancestor(F, Z) == null). Therefore still
1382 1382 # pick one (so choose p1 above).
1383 1383 if sum(1 for b in bases if b != nullrev) > 1:
1384 1384 unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
1385 1385 for i, base in enumerate(bases):
1386 1386 if base == nullrev:
1387 1387 continue
1388 1388 # Revisions in the side (not chosen as merge base) branch that
1389 1389 # might contain "surprising" contents
1390 1390 siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
1391 1391 bases, base, base, dest))
1392 1392
1393 1393 # If those revisions are covered by rebaseset, the result is good.
1394 1394 # A merge in rebaseset would be considered to cover its ancestors.
1395 1395 if siderevs:
1396 1396 rebaseset = [r for r, d in state.items()
1397 1397 if d > 0 and r not in obsskipped]
1398 1398 merges = [r for r in rebaseset
1399 1399 if cl.parentrevs(r)[1] != nullrev]
1400 1400 unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
1401 1401 siderevs, merges, rebaseset))
1402 1402
1403 1403 # Choose a merge base that has a minimal number of unwanted revs.
1404 1404 l, i = min((len(revs), i)
1405 1405 for i, revs in enumerate(unwanted) if revs is not None)
1406 1406 base = bases[i]
1407 1407
1408 1408 # newps[0] should match merge base if possible. Currently, if newps[i]
1409 1409 # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
1410 1410 # the other's ancestor. In that case, it's fine to not swap newps here.
1411 1411 # (see CASE-1 and CASE-2 above)
1412 1412 if i != 0 and newps[i] != nullrev:
1413 1413 newps[0], newps[i] = newps[i], newps[0]
1414 1414
1415 1415 # The merge will include unwanted revisions. Abort now. Revisit this if
1416 1416 # we have a more advanced merge algorithm that handles multiple bases.
1417 1417 if l > 0:
1418 1418 unwanteddesc = _(' or ').join(
1419 1419 (', '.join('%d:%s' % (r, repo[r]) for r in revs)
1420 1420 for revs in unwanted if revs is not None))
1421 1421 raise error.Abort(
1422 1422 _('rebasing %d:%s will include unwanted changes from %s')
1423 1423 % (rev, repo[rev], unwanteddesc))
1424 1424
1425 1425 repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
1426 1426
1427 1427 return newps[0], newps[1], base
1428 1428
1429 1429 def isagitpatch(repo, patchname):
1430 1430 'Return true if the given patch is in git format'
1431 1431 mqpatch = os.path.join(repo.mq.path, patchname)
1432 1432 for line in patch.linereader(open(mqpatch, 'rb')):
1433 1433 if line.startswith('diff --git'):
1434 1434 return True
1435 1435 return False
1436 1436
1437 1437 def updatemq(repo, state, skipped, **opts):
1438 1438 'Update rebased mq patches - finalize and then import them'
1439 1439 mqrebase = {}
1440 1440 mq = repo.mq
1441 1441 original_series = mq.fullseries[:]
1442 1442 skippedpatches = set()
1443 1443
1444 1444 for p in mq.applied:
1445 1445 rev = repo[p.node].rev()
1446 1446 if rev in state:
1447 1447 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1448 1448 (rev, p.name))
1449 1449 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1450 1450 else:
1451 1451 # Applied but not rebased, not sure this should happen
1452 1452 skippedpatches.add(p.name)
1453 1453
1454 1454 if mqrebase:
1455 1455 mq.finish(repo, mqrebase.keys())
1456 1456
1457 1457 # We must start import from the newest revision
1458 1458 for rev in sorted(mqrebase, reverse=True):
1459 1459 if rev not in skipped:
1460 1460 name, isgit = mqrebase[rev]
1461 1461 repo.ui.note(_('updating mq patch %s to %d:%s\n') %
1462 1462 (name, state[rev], repo[state[rev]]))
1463 1463 mq.qimport(repo, (), patchname=name, git=isgit,
1464 1464 rev=["%d" % state[rev]])
1465 1465 else:
1466 1466 # Rebased and skipped
1467 1467 skippedpatches.add(mqrebase[rev][0])
1468 1468
1469 1469 # Patches were either applied and rebased and imported in
1470 1470 # order, applied and removed or unapplied. Discard the removed
1471 1471 # ones while preserving the original series order and guards.
1472 1472 newseries = [s for s in original_series
1473 1473 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1474 1474 mq.fullseries[:] = newseries
1475 1475 mq.seriesdirty = True
1476 1476 mq.savedirty()
1477 1477
1478 1478 def storecollapsemsg(repo, collapsemsg):
1479 1479 'Store the collapse message to allow recovery'
1480 1480 collapsemsg = collapsemsg or ''
1481 1481 f = repo.vfs("last-message.txt", "w")
1482 1482 f.write("%s\n" % collapsemsg)
1483 1483 f.close()
1484 1484
1485 1485 def clearcollapsemsg(repo):
1486 1486 'Remove collapse message file'
1487 1487 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1488 1488
1489 1489 def restorecollapsemsg(repo, isabort):
1490 1490 'Restore previously stored collapse message'
1491 1491 try:
1492 1492 f = repo.vfs("last-message.txt")
1493 1493 collapsemsg = f.readline().strip()
1494 1494 f.close()
1495 1495 except IOError as err:
1496 1496 if err.errno != errno.ENOENT:
1497 1497 raise
1498 1498 if isabort:
1499 1499 # Oh well, just abort like normal
1500 1500 collapsemsg = ''
1501 1501 else:
1502 1502 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1503 1503 return collapsemsg
1504 1504
1505 1505 def clearstatus(repo):
1506 1506 'Remove the status files'
1507 1507 # Make sure the active transaction won't write the state file
1508 1508 tr = repo.currenttransaction()
1509 1509 if tr:
1510 1510 tr.removefilegenerator('rebasestate')
1511 1511 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1512 1512
1513 1513 def needupdate(repo, state):
1514 1514 '''check whether we should `update --clean` away from a merge, or if
1515 1515 somehow the working dir got forcibly updated, e.g. by older hg'''
1516 1516 parents = [p.rev() for p in repo[None].parents()]
1517 1517
1518 1518 # Are we in a merge state at all?
1519 1519 if len(parents) < 2:
1520 1520 return False
1521 1521
1522 1522 # We should be standing on the first as-of-yet unrebased commit.
1523 1523 firstunrebased = min([old for old, new in state.iteritems()
1524 1524 if new == nullrev])
1525 1525 if firstunrebased in parents:
1526 1526 return True
1527 1527
1528 1528 return False
1529 1529
1530 1530 def abort(repo, originalwd, destmap, state, activebookmark=None):
1531 1531 '''Restore the repository to its original state. Additional args:
1532 1532
1533 1533 activebookmark: the name of the bookmark that should be active after the
1534 1534 restore'''
1535 1535
1536 1536 try:
1537 1537 # If the first commits in the rebased set get skipped during the rebase,
1538 1538 # their values within the state mapping will be the dest rev id. The
1539 1539 # rebased list must must not contain the dest rev (issue4896)
1540 1540 rebased = [s for r, s in state.items()
1541 1541 if s >= 0 and s != r and s != destmap[r]]
1542 1542 immutable = [d for d in rebased if not repo[d].mutable()]
1543 1543 cleanup = True
1544 1544 if immutable:
1545 1545 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1546 1546 % ', '.join(bytes(repo[r]) for r in immutable),
1547 1547 hint=_("see 'hg help phases' for details"))
1548 1548 cleanup = False
1549 1549
1550 1550 descendants = set()
1551 1551 if rebased:
1552 1552 descendants = set(repo.changelog.descendants(rebased))
1553 1553 if descendants - set(rebased):
1554 1554 repo.ui.warn(_("warning: new changesets detected on destination "
1555 1555 "branch, can't strip\n"))
1556 1556 cleanup = False
1557 1557
1558 1558 if cleanup:
1559 1559 shouldupdate = False
1560 1560 if rebased:
1561 1561 strippoints = [
1562 1562 c.node() for c in repo.set('roots(%ld)', rebased)]
1563 1563
1564 1564 updateifonnodes = set(rebased)
1565 1565 updateifonnodes.update(destmap.values())
1566 1566 updateifonnodes.add(originalwd)
1567 1567 shouldupdate = repo['.'].rev() in updateifonnodes
1568 1568
1569 1569 # Update away from the rebase if necessary
1570 1570 if shouldupdate or needupdate(repo, state):
1571 1571 mergemod.update(repo, originalwd, False, True)
1572 1572
1573 1573 # Strip from the first rebased revision
1574 1574 if rebased:
1575 1575 # no backup of rebased cset versions needed
1576 1576 repair.strip(repo.ui, repo, strippoints)
1577 1577
1578 1578 if activebookmark and activebookmark in repo._bookmarks:
1579 1579 bookmarks.activate(repo, activebookmark)
1580 1580
1581 1581 finally:
1582 1582 clearstatus(repo)
1583 1583 clearcollapsemsg(repo)
1584 1584 repo.ui.warn(_('rebase aborted\n'))
1585 1585 return 0
1586 1586
1587 1587 def sortsource(destmap):
1588 1588 """yield source revisions in an order that we only rebase things once
1589 1589
1590 1590 If source and destination overlaps, we should filter out revisions
1591 1591 depending on other revisions which hasn't been rebased yet.
1592 1592
1593 1593 Yield a sorted list of revisions each time.
1594 1594
1595 1595 For example, when rebasing A to B, B to C. This function yields [B], then
1596 1596 [A], indicating B needs to be rebased first.
1597 1597
1598 1598 Raise if there is a cycle so the rebase is impossible.
1599 1599 """
1600 1600 srcset = set(destmap)
1601 1601 while srcset:
1602 1602 srclist = sorted(srcset)
1603 1603 result = []
1604 1604 for r in srclist:
1605 1605 if destmap[r] not in srcset:
1606 1606 result.append(r)
1607 1607 if not result:
1608 1608 raise error.Abort(_('source and destination form a cycle'))
1609 1609 srcset -= set(result)
1610 1610 yield result
1611 1611
1612 1612 def buildstate(repo, destmap, collapse):
1613 1613 '''Define which revisions are going to be rebased and where
1614 1614
1615 1615 repo: repo
1616 1616 destmap: {srcrev: destrev}
1617 1617 '''
1618 1618 rebaseset = destmap.keys()
1619 1619 originalwd = repo['.'].rev()
1620 1620
1621 1621 # This check isn't strictly necessary, since mq detects commits over an
1622 1622 # applied patch. But it prevents messing up the working directory when
1623 1623 # a partially completed rebase is blocked by mq.
1624 1624 if 'qtip' in repo.tags():
1625 1625 mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
1626 1626 if set(destmap.values()) & mqapplied:
1627 1627 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1628 1628
1629 1629 # Get "cycle" error early by exhausting the generator.
1630 1630 sortedsrc = list(sortsource(destmap)) # a list of sorted revs
1631 1631 if not sortedsrc:
1632 1632 raise error.Abort(_('no matching revisions'))
1633 1633
1634 1634 # Only check the first batch of revisions to rebase not depending on other
1635 1635 # rebaseset. This means "source is ancestor of destination" for the second
1636 1636 # (and following) batches of revisions are not checked here. We rely on
1637 1637 # "defineparents" to do that check.
1638 1638 roots = list(repo.set('roots(%ld)', sortedsrc[0]))
1639 1639 if not roots:
1640 1640 raise error.Abort(_('no matching revisions'))
1641 1641 def revof(r):
1642 1642 return r.rev()
1643 1643 roots = sorted(roots, key=revof)
1644 1644 state = dict.fromkeys(rebaseset, revtodo)
1645 1645 emptyrebase = (len(sortedsrc) == 1)
1646 1646 for root in roots:
1647 1647 dest = repo[destmap[root.rev()]]
1648 1648 commonbase = root.ancestor(dest)
1649 1649 if commonbase == root:
1650 1650 raise error.Abort(_('source is ancestor of destination'))
1651 1651 if commonbase == dest:
1652 1652 wctx = repo[None]
1653 1653 if dest == wctx.p1():
1654 1654 # when rebasing to '.', it will use the current wd branch name
1655 1655 samebranch = root.branch() == wctx.branch()
1656 1656 else:
1657 1657 samebranch = root.branch() == dest.branch()
1658 1658 if not collapse and samebranch and dest in root.parents():
1659 1659 # mark the revision as done by setting its new revision
1660 1660 # equal to its old (current) revisions
1661 1661 state[root.rev()] = root.rev()
1662 1662 repo.ui.debug('source is a child of destination\n')
1663 1663 continue
1664 1664
1665 1665 emptyrebase = False
1666 1666 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1667 1667 if emptyrebase:
1668 1668 return None
1669 1669 for rev in sorted(state):
1670 1670 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1671 1671 # if all parents of this revision are done, then so is this revision
1672 1672 if parents and all((state.get(p) == p for p in parents)):
1673 1673 state[rev] = rev
1674 1674 return originalwd, destmap, state
1675 1675
1676 1676 def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None,
1677 1677 keepf=False, fm=None):
1678 1678 """dispose of rebased revision at the end of the rebase
1679 1679
1680 1680 If `collapsedas` is not None, the rebase was a collapse whose result if the
1681 1681 `collapsedas` node.
1682 1682
1683 1683 If `keepf` is not True, the rebase has --keep set and no nodes should be
1684 1684 removed (but bookmarks still need to be moved).
1685 1685 """
1686 1686 tonode = repo.changelog.node
1687 1687 replacements = {}
1688 1688 moves = {}
1689 1689 for rev, newrev in sorted(state.items()):
1690 1690 if newrev >= 0 and newrev != rev:
1691 1691 oldnode = tonode(rev)
1692 1692 newnode = collapsedas or tonode(newrev)
1693 1693 moves[oldnode] = newnode
1694 1694 if not keepf:
1695 1695 if rev in skipped:
1696 1696 succs = ()
1697 1697 else:
1698 1698 succs = (newnode,)
1699 1699 replacements[oldnode] = succs
1700 1700 scmutil.cleanupnodes(repo, replacements, 'rebase', moves)
1701 1701 if fm:
1702 1702 hf = fm.hexfunc
1703 1703 fl = fm.formatlist
1704 1704 fd = fm.formatdict
1705 1705 nodechanges = fd({hf(oldn): fl([hf(n) for n in newn], name='node')
1706 1706 for oldn, newn in replacements.iteritems()},
1707 1707 key="oldnode", value="newnodes")
1708 1708 fm.data(nodechanges=nodechanges)
1709 1709
1710 1710 def pullrebase(orig, ui, repo, *args, **opts):
1711 1711 'Call rebase after pull if the latter has been invoked with --rebase'
1712 1712 ret = None
1713 1713 if opts.get(r'rebase'):
1714 1714 if ui.configbool('commands', 'rebase.requiredest'):
1715 1715 msg = _('rebase destination required by configuration')
1716 1716 hint = _('use hg pull followed by hg rebase -d DEST')
1717 1717 raise error.Abort(msg, hint=hint)
1718 1718
1719 1719 with repo.wlock(), repo.lock():
1720 1720 if opts.get(r'update'):
1721 1721 del opts[r'update']
1722 1722 ui.debug('--update and --rebase are not compatible, ignoring '
1723 1723 'the update flag\n')
1724 1724
1725 1725 cmdutil.checkunfinished(repo)
1726 1726 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1727 1727 'please commit or shelve your changes first'))
1728 1728
1729 1729 revsprepull = len(repo)
1730 1730 origpostincoming = commands.postincoming
1731 1731 def _dummy(*args, **kwargs):
1732 1732 pass
1733 1733 commands.postincoming = _dummy
1734 1734 try:
1735 1735 ret = orig(ui, repo, *args, **opts)
1736 1736 finally:
1737 1737 commands.postincoming = origpostincoming
1738 1738 revspostpull = len(repo)
1739 1739 if revspostpull > revsprepull:
1740 1740 # --rev option from pull conflict with rebase own --rev
1741 1741 # dropping it
1742 1742 if r'rev' in opts:
1743 1743 del opts[r'rev']
1744 1744 # positional argument from pull conflicts with rebase's own
1745 1745 # --source.
1746 1746 if r'source' in opts:
1747 1747 del opts[r'source']
1748 1748 # revsprepull is the len of the repo, not revnum of tip.
1749 1749 destspace = list(repo.changelog.revs(start=revsprepull))
1750 1750 opts[r'_destspace'] = destspace
1751 1751 try:
1752 1752 rebase(ui, repo, **opts)
1753 1753 except error.NoMergeDestAbort:
1754 1754 # we can maybe update instead
1755 1755 rev, _a, _b = destutil.destupdate(repo)
1756 1756 if rev == repo['.'].rev():
1757 1757 ui.status(_('nothing to rebase\n'))
1758 1758 else:
1759 1759 ui.status(_('nothing to rebase - updating instead\n'))
1760 1760 # not passing argument to get the bare update behavior
1761 1761 # with warning and trumpets
1762 1762 commands.update(ui, repo)
1763 1763 else:
1764 1764 if opts.get(r'tool'):
1765 1765 raise error.Abort(_('--tool can only be used with --rebase'))
1766 1766 ret = orig(ui, repo, *args, **opts)
1767 1767
1768 1768 return ret
1769 1769
1770 1770 def _filterobsoleterevs(repo, revs):
1771 1771 """returns a set of the obsolete revisions in revs"""
1772 1772 return set(r for r in revs if repo[r].obsolete())
1773 1773
1774 1774 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
1775 1775 """Return (obsoletenotrebased, obsoletewithoutsuccessorindestination).
1776 1776
1777 1777 `obsoletenotrebased` is a mapping mapping obsolete => successor for all
1778 1778 obsolete nodes to be rebased given in `rebaseobsrevs`.
1779 1779
1780 1780 `obsoletewithoutsuccessorindestination` is a set with obsolete revisions
1781 1781 without a successor in destination.
1782 1782
1783 1783 `obsoleteextinctsuccessors` is a set of obsolete revisions with only
1784 1784 obsolete successors.
1785 1785 """
1786 1786 obsoletenotrebased = {}
1787 1787 obsoletewithoutsuccessorindestination = set([])
1788 1788 obsoleteextinctsuccessors = set([])
1789 1789
1790 1790 assert repo.filtername is None
1791 1791 cl = repo.changelog
1792 1792 nodemap = cl.nodemap
1793 1793 extinctnodes = set(cl.node(r) for r in repo.revs('extinct()'))
1794 1794 for srcrev in rebaseobsrevs:
1795 1795 srcnode = cl.node(srcrev)
1796 1796 destnode = cl.node(destmap[srcrev])
1797 1797 # XXX: more advanced APIs are required to handle split correctly
1798 1798 successors = set(obsutil.allsuccessors(repo.obsstore, [srcnode]))
1799 1799 # obsutil.allsuccessors includes node itself
1800 1800 successors.remove(srcnode)
1801 1801 if successors.issubset(extinctnodes):
1802 1802 # all successors are extinct
1803 1803 obsoleteextinctsuccessors.add(srcrev)
1804 1804 if not successors:
1805 1805 # no successor
1806 1806 obsoletenotrebased[srcrev] = None
1807 1807 else:
1808 1808 for succnode in successors:
1809 1809 if succnode not in nodemap:
1810 1810 continue
1811 1811 if cl.isancestor(succnode, destnode):
1812 1812 obsoletenotrebased[srcrev] = nodemap[succnode]
1813 1813 break
1814 1814 else:
1815 1815 # If 'srcrev' has a successor in rebase set but none in
1816 1816 # destination (which would be catched above), we shall skip it
1817 1817 # and its descendants to avoid divergence.
1818 if any(nodemap[s] in destmap for s in successors):
1818 if any(nodemap[s] in destmap for s in successors
1819 if s in nodemap):
1819 1820 obsoletewithoutsuccessorindestination.add(srcrev)
1820 1821
1821 1822 return (
1822 1823 obsoletenotrebased,
1823 1824 obsoletewithoutsuccessorindestination,
1824 1825 obsoleteextinctsuccessors,
1825 1826 )
1826 1827
1827 1828 def summaryhook(ui, repo):
1828 1829 if not repo.vfs.exists('rebasestate'):
1829 1830 return
1830 1831 try:
1831 1832 rbsrt = rebaseruntime(repo, ui, {})
1832 1833 rbsrt.restorestatus()
1833 1834 state = rbsrt.state
1834 1835 except error.RepoLookupError:
1835 1836 # i18n: column positioning for "hg summary"
1836 1837 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1837 1838 ui.write(msg)
1838 1839 return
1839 1840 numrebased = len([i for i in state.itervalues() if i >= 0])
1840 1841 # i18n: column positioning for "hg summary"
1841 1842 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1842 1843 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1843 1844 ui.label(_('%d remaining'), 'rebase.remaining') %
1844 1845 (len(state) - numrebased)))
1845 1846
1846 1847 def uisetup(ui):
1847 1848 #Replace pull with a decorator to provide --rebase option
1848 1849 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1849 1850 entry[1].append(('', 'rebase', None,
1850 1851 _("rebase working directory to branch head")))
1851 1852 entry[1].append(('t', 'tool', '',
1852 1853 _("specify merge tool for rebase")))
1853 1854 cmdutil.summaryhooks.add('rebase', summaryhook)
1854 1855 cmdutil.unfinishedstates.append(
1855 1856 ['rebasestate', False, False, _('rebase in progress'),
1856 1857 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1857 1858 cmdutil.afterresolvedstates.append(
1858 1859 ['rebasestate', _('hg rebase --continue')])
@@ -1,1758 +1,1790
1 1 ==========================
2 2 Test rebase with obsolete
3 3 ==========================
4 4
5 5 Enable obsolete
6 6
7 7 $ cat >> $HGRCPATH << EOF
8 8 > [ui]
9 9 > logtemplate= {rev}:{node|short} {desc|firstline}{if(obsolete,' ({obsfate})')}
10 10 > [experimental]
11 11 > evolution.createmarkers=True
12 12 > evolution.allowunstable=True
13 13 > [phases]
14 14 > publish=False
15 15 > [extensions]
16 16 > rebase=
17 17 > drawdag=$TESTDIR/drawdag.py
18 18 > EOF
19 19
20 20 Setup rebase canonical repo
21 21
22 22 $ hg init base
23 23 $ cd base
24 24 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
25 25 adding changesets
26 26 adding manifests
27 27 adding file changes
28 28 added 8 changesets with 7 changes to 7 files (+2 heads)
29 29 new changesets cd010b8cd998:02de42196ebe
30 30 (run 'hg heads' to see heads, 'hg merge' to merge)
31 31 $ hg up tip
32 32 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
33 33 $ hg log -G
34 34 @ 7:02de42196ebe H
35 35 |
36 36 | o 6:eea13746799a G
37 37 |/|
38 38 o | 5:24b6387c8c8c F
39 39 | |
40 40 | o 4:9520eea781bc E
41 41 |/
42 42 | o 3:32af7686d403 D
43 43 | |
44 44 | o 2:5fddd98957c8 C
45 45 | |
46 46 | o 1:42ccdea3bb16 B
47 47 |/
48 48 o 0:cd010b8cd998 A
49 49
50 50 $ cd ..
51 51
52 52 simple rebase
53 53 ---------------------------------
54 54
55 55 $ hg clone base simple
56 56 updating to branch default
57 57 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
58 58 $ cd simple
59 59 $ hg up 32af7686d403
60 60 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
61 61 $ hg rebase -d eea13746799a
62 62 rebasing 1:42ccdea3bb16 "B"
63 63 rebasing 2:5fddd98957c8 "C"
64 64 rebasing 3:32af7686d403 "D"
65 65 $ hg log -G
66 66 @ 10:8eeb3c33ad33 D
67 67 |
68 68 o 9:2327fea05063 C
69 69 |
70 70 o 8:e4e5be0395b2 B
71 71 |
72 72 | o 7:02de42196ebe H
73 73 | |
74 74 o | 6:eea13746799a G
75 75 |\|
76 76 | o 5:24b6387c8c8c F
77 77 | |
78 78 o | 4:9520eea781bc E
79 79 |/
80 80 o 0:cd010b8cd998 A
81 81
82 82 $ hg log --hidden -G
83 83 @ 10:8eeb3c33ad33 D
84 84 |
85 85 o 9:2327fea05063 C
86 86 |
87 87 o 8:e4e5be0395b2 B
88 88 |
89 89 | o 7:02de42196ebe H
90 90 | |
91 91 o | 6:eea13746799a G
92 92 |\|
93 93 | o 5:24b6387c8c8c F
94 94 | |
95 95 o | 4:9520eea781bc E
96 96 |/
97 97 | x 3:32af7686d403 D (rewritten using rebase as 10:8eeb3c33ad33)
98 98 | |
99 99 | x 2:5fddd98957c8 C (rewritten using rebase as 9:2327fea05063)
100 100 | |
101 101 | x 1:42ccdea3bb16 B (rewritten using rebase as 8:e4e5be0395b2)
102 102 |/
103 103 o 0:cd010b8cd998 A
104 104
105 105 $ hg debugobsolete
106 106 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 e4e5be0395b2cbd471ed22a26b1b6a1a0658a794 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
107 107 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 2327fea05063f39961b14cb69435a9898dc9a245 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
108 108 32af7686d403cf45b5d95f2d70cebea587ac806a 8eeb3c33ad33d452c89e5dcf611c347f978fb42b 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
109 109
110 110
111 111 $ cd ..
112 112
113 113 empty changeset
114 114 ---------------------------------
115 115
116 116 $ hg clone base empty
117 117 updating to branch default
118 118 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
119 119 $ cd empty
120 120 $ hg up eea13746799a
121 121 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
122 122
123 123 We make a copy of both the first changeset in the rebased and some other in the
124 124 set.
125 125
126 126 $ hg graft 42ccdea3bb16 32af7686d403
127 127 grafting 1:42ccdea3bb16 "B"
128 128 grafting 3:32af7686d403 "D"
129 129 $ hg rebase -s 42ccdea3bb16 -d .
130 130 rebasing 1:42ccdea3bb16 "B"
131 131 note: rebase of 1:42ccdea3bb16 created no changes to commit
132 132 rebasing 2:5fddd98957c8 "C"
133 133 rebasing 3:32af7686d403 "D"
134 134 note: rebase of 3:32af7686d403 created no changes to commit
135 135 $ hg log -G
136 136 o 10:5ae4c968c6ac C
137 137 |
138 138 @ 9:08483444fef9 D
139 139 |
140 140 o 8:8877864f1edb B
141 141 |
142 142 | o 7:02de42196ebe H
143 143 | |
144 144 o | 6:eea13746799a G
145 145 |\|
146 146 | o 5:24b6387c8c8c F
147 147 | |
148 148 o | 4:9520eea781bc E
149 149 |/
150 150 o 0:cd010b8cd998 A
151 151
152 152 $ hg log --hidden -G
153 153 o 10:5ae4c968c6ac C
154 154 |
155 155 @ 9:08483444fef9 D
156 156 |
157 157 o 8:8877864f1edb B
158 158 |
159 159 | o 7:02de42196ebe H
160 160 | |
161 161 o | 6:eea13746799a G
162 162 |\|
163 163 | o 5:24b6387c8c8c F
164 164 | |
165 165 o | 4:9520eea781bc E
166 166 |/
167 167 | x 3:32af7686d403 D (pruned using rebase)
168 168 | |
169 169 | x 2:5fddd98957c8 C (rewritten using rebase as 10:5ae4c968c6ac)
170 170 | |
171 171 | x 1:42ccdea3bb16 B (pruned using rebase)
172 172 |/
173 173 o 0:cd010b8cd998 A
174 174
175 175 $ hg debugobsolete
176 176 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'rebase', 'user': 'test'}
177 177 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
178 178 32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'rebase', 'user': 'test'}
179 179
180 180
181 181 More complex case where part of the rebase set were already rebased
182 182
183 183 $ hg rebase --rev 'desc(D)' --dest 'desc(H)'
184 184 rebasing 9:08483444fef9 "D"
185 185 1 new orphan changesets
186 186 $ hg debugobsolete
187 187 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'rebase', 'user': 'test'}
188 188 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
189 189 32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'rebase', 'user': 'test'}
190 190 08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
191 191 $ hg log -G
192 192 @ 11:4596109a6a43 D
193 193 |
194 194 | * 10:5ae4c968c6ac C
195 195 | |
196 196 | x 9:08483444fef9 D (rewritten using rebase as 11:4596109a6a43)
197 197 | |
198 198 | o 8:8877864f1edb B
199 199 | |
200 200 o | 7:02de42196ebe H
201 201 | |
202 202 | o 6:eea13746799a G
203 203 |/|
204 204 o | 5:24b6387c8c8c F
205 205 | |
206 206 | o 4:9520eea781bc E
207 207 |/
208 208 o 0:cd010b8cd998 A
209 209
210 210 $ hg rebase --source 'desc(B)' --dest 'tip' --config experimental.rebaseskipobsolete=True
211 211 rebasing 8:8877864f1edb "B"
212 212 note: not rebasing 9:08483444fef9 "D", already in destination as 11:4596109a6a43 "D" (tip)
213 213 rebasing 10:5ae4c968c6ac "C"
214 214 $ hg debugobsolete
215 215 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'rebase', 'user': 'test'}
216 216 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
217 217 32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'rebase', 'user': 'test'}
218 218 08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
219 219 8877864f1edb05d0e07dc4ba77b67a80a7b86672 462a34d07e599b87ea08676a449373fe4e2e1347 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
220 220 5ae4c968c6aca831df823664e706c9d4aa34473d 98f6af4ee9539e14da4465128f894c274900b6e5 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
221 221 $ hg log --rev 'contentdivergent()'
222 222 $ hg log -G
223 223 o 13:98f6af4ee953 C
224 224 |
225 225 o 12:462a34d07e59 B
226 226 |
227 227 @ 11:4596109a6a43 D
228 228 |
229 229 o 7:02de42196ebe H
230 230 |
231 231 | o 6:eea13746799a G
232 232 |/|
233 233 o | 5:24b6387c8c8c F
234 234 | |
235 235 | o 4:9520eea781bc E
236 236 |/
237 237 o 0:cd010b8cd998 A
238 238
239 239 $ hg log --style default --debug -r 4596109a6a4328c398bde3a4a3b6737cfade3003
240 240 changeset: 11:4596109a6a4328c398bde3a4a3b6737cfade3003
241 241 phase: draft
242 242 parent: 7:02de42196ebee42ef284b6780a87cdc96e8eaab6
243 243 parent: -1:0000000000000000000000000000000000000000
244 244 manifest: 11:a91006e3a02f1edf631f7018e6e5684cf27dd905
245 245 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
246 246 date: Sat Apr 30 15:24:48 2011 +0200
247 247 files+: D
248 248 extra: branch=default
249 249 extra: rebase_source=08483444fef91d6224f6655ee586a65d263ad34c
250 250 extra: source=32af7686d403cf45b5d95f2d70cebea587ac806a
251 251 description:
252 252 D
253 253
254 254
255 255 $ hg up -qr 'desc(G)'
256 256 $ hg graft 4596109a6a4328c398bde3a4a3b6737cfade3003
257 257 grafting 11:4596109a6a43 "D"
258 258 $ hg up -qr 'desc(E)'
259 259 $ hg rebase -s tip -d .
260 260 rebasing 14:9e36056a46e3 "D" (tip)
261 261 $ hg log --style default --debug -r tip
262 262 changeset: 15:627d4614809036ba22b9e7cb31638ddc06ab99ab
263 263 tag: tip
264 264 phase: draft
265 265 parent: 4:9520eea781bcca16c1e15acc0ba14335a0e8e5ba
266 266 parent: -1:0000000000000000000000000000000000000000
267 267 manifest: 15:648e8ede73ae3e497d093d3a4c8fcc2daa864f42
268 268 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
269 269 date: Sat Apr 30 15:24:48 2011 +0200
270 270 files+: D
271 271 extra: branch=default
272 272 extra: intermediate-source=4596109a6a4328c398bde3a4a3b6737cfade3003
273 273 extra: rebase_source=9e36056a46e37c9776168c7375734eebc70e294f
274 274 extra: source=32af7686d403cf45b5d95f2d70cebea587ac806a
275 275 description:
276 276 D
277 277
278 278
279 279 Start rebase from a commit that is obsolete but not hidden only because it's
280 280 a working copy parent. We should be moved back to the starting commit as usual
281 281 even though it is hidden (until we're moved there).
282 282
283 283 $ hg --hidden up -qr 'first(hidden())'
284 284 updating to a hidden changeset 42ccdea3bb16
285 285 (hidden revision '42ccdea3bb16' is pruned)
286 286 $ hg rebase --rev 13 --dest 15
287 287 rebasing 13:98f6af4ee953 "C"
288 288 $ hg log -G
289 289 o 16:294a2b93eb4d C
290 290 |
291 291 o 15:627d46148090 D
292 292 |
293 293 | o 12:462a34d07e59 B
294 294 | |
295 295 | o 11:4596109a6a43 D
296 296 | |
297 297 | o 7:02de42196ebe H
298 298 | |
299 299 +---o 6:eea13746799a G
300 300 | |/
301 301 | o 5:24b6387c8c8c F
302 302 | |
303 303 o | 4:9520eea781bc E
304 304 |/
305 305 | @ 1:42ccdea3bb16 B (pruned using rebase)
306 306 |/
307 307 o 0:cd010b8cd998 A
308 308
309 309
310 310 $ cd ..
311 311
312 312 collapse rebase
313 313 ---------------------------------
314 314
315 315 $ hg clone base collapse
316 316 updating to branch default
317 317 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
318 318 $ cd collapse
319 319 $ hg rebase -s 42ccdea3bb16 -d eea13746799a --collapse
320 320 rebasing 1:42ccdea3bb16 "B"
321 321 rebasing 2:5fddd98957c8 "C"
322 322 rebasing 3:32af7686d403 "D"
323 323 $ hg log -G
324 324 o 8:4dc2197e807b Collapsed revision
325 325 |
326 326 | @ 7:02de42196ebe H
327 327 | |
328 328 o | 6:eea13746799a G
329 329 |\|
330 330 | o 5:24b6387c8c8c F
331 331 | |
332 332 o | 4:9520eea781bc E
333 333 |/
334 334 o 0:cd010b8cd998 A
335 335
336 336 $ hg log --hidden -G
337 337 o 8:4dc2197e807b Collapsed revision
338 338 |
339 339 | @ 7:02de42196ebe H
340 340 | |
341 341 o | 6:eea13746799a G
342 342 |\|
343 343 | o 5:24b6387c8c8c F
344 344 | |
345 345 o | 4:9520eea781bc E
346 346 |/
347 347 | x 3:32af7686d403 D (rewritten using rebase as 8:4dc2197e807b)
348 348 | |
349 349 | x 2:5fddd98957c8 C (rewritten using rebase as 8:4dc2197e807b)
350 350 | |
351 351 | x 1:42ccdea3bb16 B (rewritten using rebase as 8:4dc2197e807b)
352 352 |/
353 353 o 0:cd010b8cd998 A
354 354
355 355 $ hg id --debug -r tip
356 356 4dc2197e807bae9817f09905b50ab288be2dbbcf tip
357 357 $ hg debugobsolete
358 358 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '13', 'operation': 'rebase', 'user': 'test'}
359 359 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '13', 'operation': 'rebase', 'user': 'test'}
360 360 32af7686d403cf45b5d95f2d70cebea587ac806a 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '13', 'operation': 'rebase', 'user': 'test'}
361 361
362 362 $ cd ..
363 363
364 364 Rebase set has hidden descendants
365 365 ---------------------------------
366 366
367 367 We rebase a changeset which has hidden descendants. Hidden changesets must not
368 368 be rebased.
369 369
370 370 $ hg clone base hidden
371 371 updating to branch default
372 372 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
373 373 $ cd hidden
374 374 $ hg log -G
375 375 @ 7:02de42196ebe H
376 376 |
377 377 | o 6:eea13746799a G
378 378 |/|
379 379 o | 5:24b6387c8c8c F
380 380 | |
381 381 | o 4:9520eea781bc E
382 382 |/
383 383 | o 3:32af7686d403 D
384 384 | |
385 385 | o 2:5fddd98957c8 C
386 386 | |
387 387 | o 1:42ccdea3bb16 B
388 388 |/
389 389 o 0:cd010b8cd998 A
390 390
391 391 $ hg rebase -s 5fddd98957c8 -d eea13746799a
392 392 rebasing 2:5fddd98957c8 "C"
393 393 rebasing 3:32af7686d403 "D"
394 394 $ hg log -G
395 395 o 9:cf44d2f5a9f4 D
396 396 |
397 397 o 8:e273c5e7d2d2 C
398 398 |
399 399 | @ 7:02de42196ebe H
400 400 | |
401 401 o | 6:eea13746799a G
402 402 |\|
403 403 | o 5:24b6387c8c8c F
404 404 | |
405 405 o | 4:9520eea781bc E
406 406 |/
407 407 | o 1:42ccdea3bb16 B
408 408 |/
409 409 o 0:cd010b8cd998 A
410 410
411 411 $ hg rebase -s 42ccdea3bb16 -d 02de42196ebe
412 412 rebasing 1:42ccdea3bb16 "B"
413 413 $ hg log -G
414 414 o 10:7c6027df6a99 B
415 415 |
416 416 | o 9:cf44d2f5a9f4 D
417 417 | |
418 418 | o 8:e273c5e7d2d2 C
419 419 | |
420 420 @ | 7:02de42196ebe H
421 421 | |
422 422 | o 6:eea13746799a G
423 423 |/|
424 424 o | 5:24b6387c8c8c F
425 425 | |
426 426 | o 4:9520eea781bc E
427 427 |/
428 428 o 0:cd010b8cd998 A
429 429
430 430 $ hg log --hidden -G
431 431 o 10:7c6027df6a99 B
432 432 |
433 433 | o 9:cf44d2f5a9f4 D
434 434 | |
435 435 | o 8:e273c5e7d2d2 C
436 436 | |
437 437 @ | 7:02de42196ebe H
438 438 | |
439 439 | o 6:eea13746799a G
440 440 |/|
441 441 o | 5:24b6387c8c8c F
442 442 | |
443 443 | o 4:9520eea781bc E
444 444 |/
445 445 | x 3:32af7686d403 D (rewritten using rebase as 9:cf44d2f5a9f4)
446 446 | |
447 447 | x 2:5fddd98957c8 C (rewritten using rebase as 8:e273c5e7d2d2)
448 448 | |
449 449 | x 1:42ccdea3bb16 B (rewritten using rebase as 10:7c6027df6a99)
450 450 |/
451 451 o 0:cd010b8cd998 A
452 452
453 453 $ hg debugobsolete
454 454 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b e273c5e7d2d29df783dce9f9eaa3ac4adc69c15d 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
455 455 32af7686d403cf45b5d95f2d70cebea587ac806a cf44d2f5a9f4297a62be94cbdd3dff7c7dc54258 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
456 456 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 7c6027df6a99d93f461868e5433f63bde20b6dfb 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
457 457
458 458 Test that rewriting leaving instability behind is allowed
459 459 ---------------------------------------------------------------------
460 460
461 461 $ hg log -r 'children(8)'
462 462 9:cf44d2f5a9f4 D (no-eol)
463 463 $ hg rebase -r 8
464 464 rebasing 8:e273c5e7d2d2 "C"
465 465 1 new orphan changesets
466 466 $ hg log -G
467 467 o 11:0d8f238b634c C
468 468 |
469 469 o 10:7c6027df6a99 B
470 470 |
471 471 | * 9:cf44d2f5a9f4 D
472 472 | |
473 473 | x 8:e273c5e7d2d2 C (rewritten using rebase as 11:0d8f238b634c)
474 474 | |
475 475 @ | 7:02de42196ebe H
476 476 | |
477 477 | o 6:eea13746799a G
478 478 |/|
479 479 o | 5:24b6387c8c8c F
480 480 | |
481 481 | o 4:9520eea781bc E
482 482 |/
483 483 o 0:cd010b8cd998 A
484 484
485 485 $ cd ..
486 486 $ cp -R hidden stabilize
487 487 $ cd stabilize
488 488 $ hg rebase --auto-orphans '0::' -d 10
489 489 abort: --auto-orphans is incompatible with --dest
490 490 [255]
491 491 $ hg rebase --auto-orphans '0::'
492 492 rebasing 9:cf44d2f5a9f4 "D"
493 493 $ hg log -G
494 494 o 12:7e3935feaa68 D
495 495 |
496 496 o 11:0d8f238b634c C
497 497 |
498 498 o 10:7c6027df6a99 B
499 499 |
500 500 @ 7:02de42196ebe H
501 501 |
502 502 | o 6:eea13746799a G
503 503 |/|
504 504 o | 5:24b6387c8c8c F
505 505 | |
506 506 | o 4:9520eea781bc E
507 507 |/
508 508 o 0:cd010b8cd998 A
509 509
510 510
511 511 $ cd ../hidden
512 512 $ rm -r ../stabilize
513 513
514 514 Test multiple root handling
515 515 ------------------------------------
516 516
517 517 $ hg rebase --dest 4 --rev '7+11+9'
518 518 rebasing 9:cf44d2f5a9f4 "D"
519 519 rebasing 7:02de42196ebe "H"
520 520 rebasing 11:0d8f238b634c "C" (tip)
521 521 $ hg log -G
522 522 o 14:1e8370e38cca C
523 523 |
524 524 @ 13:bfe264faf697 H
525 525 |
526 526 | o 12:102b4c1d889b D
527 527 |/
528 528 | * 10:7c6027df6a99 B
529 529 | |
530 530 | x 7:02de42196ebe H (rewritten using rebase as 13:bfe264faf697)
531 531 | |
532 532 +---o 6:eea13746799a G
533 533 | |/
534 534 | o 5:24b6387c8c8c F
535 535 | |
536 536 o | 4:9520eea781bc E
537 537 |/
538 538 o 0:cd010b8cd998 A
539 539
540 540 $ cd ..
541 541
542 542 Detach both parents
543 543
544 544 $ hg init double-detach
545 545 $ cd double-detach
546 546
547 547 $ hg debugdrawdag <<EOF
548 548 > F
549 549 > /|
550 550 > C E
551 551 > | |
552 552 > B D G
553 553 > \|/
554 554 > A
555 555 > EOF
556 556
557 557 $ hg rebase -d G -r 'B + D + F'
558 558 rebasing 1:112478962961 "B" (B)
559 559 rebasing 2:b18e25de2cf5 "D" (D)
560 560 rebasing 6:f15c3adaf214 "F" (F tip)
561 561 abort: cannot rebase 6:f15c3adaf214 without moving at least one of its parents
562 562 [255]
563 563
564 564 $ cd ..
565 565
566 566 test on rebase dropping a merge
567 567
568 568 (setup)
569 569
570 570 $ hg init dropmerge
571 571 $ cd dropmerge
572 572 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
573 573 adding changesets
574 574 adding manifests
575 575 adding file changes
576 576 added 8 changesets with 7 changes to 7 files (+2 heads)
577 577 new changesets cd010b8cd998:02de42196ebe
578 578 (run 'hg heads' to see heads, 'hg merge' to merge)
579 579 $ hg up 3
580 580 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
581 581 $ hg merge 7
582 582 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
583 583 (branch merge, don't forget to commit)
584 584 $ hg ci -m 'M'
585 585 $ echo I > I
586 586 $ hg add I
587 587 $ hg ci -m I
588 588 $ hg log -G
589 589 @ 9:4bde274eefcf I
590 590 |
591 591 o 8:53a6a128b2b7 M
592 592 |\
593 593 | o 7:02de42196ebe H
594 594 | |
595 595 | | o 6:eea13746799a G
596 596 | |/|
597 597 | o | 5:24b6387c8c8c F
598 598 | | |
599 599 | | o 4:9520eea781bc E
600 600 | |/
601 601 o | 3:32af7686d403 D
602 602 | |
603 603 o | 2:5fddd98957c8 C
604 604 | |
605 605 o | 1:42ccdea3bb16 B
606 606 |/
607 607 o 0:cd010b8cd998 A
608 608
609 609 (actual test)
610 610
611 611 $ hg rebase --dest 6 --rev '((desc(H) + desc(D))::) - desc(M)'
612 612 rebasing 3:32af7686d403 "D"
613 613 rebasing 7:02de42196ebe "H"
614 614 rebasing 9:4bde274eefcf "I" (tip)
615 615 1 new orphan changesets
616 616 $ hg log -G
617 617 @ 12:acd174b7ab39 I
618 618 |
619 619 o 11:6c11a6218c97 H
620 620 |
621 621 | o 10:b5313c85b22e D
622 622 |/
623 623 | * 8:53a6a128b2b7 M
624 624 | |\
625 625 | | x 7:02de42196ebe H (rewritten using rebase as 11:6c11a6218c97)
626 626 | | |
627 627 o---+ 6:eea13746799a G
628 628 | | |
629 629 | | o 5:24b6387c8c8c F
630 630 | | |
631 631 o---+ 4:9520eea781bc E
632 632 / /
633 633 x | 3:32af7686d403 D (rewritten using rebase as 10:b5313c85b22e)
634 634 | |
635 635 o | 2:5fddd98957c8 C
636 636 | |
637 637 o | 1:42ccdea3bb16 B
638 638 |/
639 639 o 0:cd010b8cd998 A
640 640
641 641
642 642 Test hidden changesets in the rebase set (issue4504)
643 643
644 644 $ hg up --hidden 9
645 645 updating to a hidden changeset 4bde274eefcf
646 646 (hidden revision '4bde274eefcf' was rewritten as: acd174b7ab39)
647 647 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
648 648 $ echo J > J
649 649 $ hg add J
650 650 $ hg commit -m J
651 651 1 new orphan changesets
652 652 $ hg debugobsolete `hg log --rev . -T '{node}'`
653 653 obsoleted 1 changesets
654 654
655 655 $ hg rebase --rev .~1::. --dest 'max(desc(D))' --traceback --config experimental.rebaseskipobsolete=off
656 656 rebasing 9:4bde274eefcf "I"
657 657 rebasing 13:06edfc82198f "J" (tip)
658 658 2 new content-divergent changesets
659 659 $ hg log -G
660 660 @ 15:5ae8a643467b J
661 661 |
662 662 * 14:9ad579b4a5de I
663 663 |
664 664 | * 12:acd174b7ab39 I
665 665 | |
666 666 | o 11:6c11a6218c97 H
667 667 | |
668 668 o | 10:b5313c85b22e D
669 669 |/
670 670 | * 8:53a6a128b2b7 M
671 671 | |\
672 672 | | x 7:02de42196ebe H (rewritten using rebase as 11:6c11a6218c97)
673 673 | | |
674 674 o---+ 6:eea13746799a G
675 675 | | |
676 676 | | o 5:24b6387c8c8c F
677 677 | | |
678 678 o---+ 4:9520eea781bc E
679 679 / /
680 680 x | 3:32af7686d403 D (rewritten using rebase as 10:b5313c85b22e)
681 681 | |
682 682 o | 2:5fddd98957c8 C
683 683 | |
684 684 o | 1:42ccdea3bb16 B
685 685 |/
686 686 o 0:cd010b8cd998 A
687 687
688 688 $ hg up 14 -C
689 689 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
690 690 $ echo "K" > K
691 691 $ hg add K
692 692 $ hg commit --amend -m "K"
693 693 1 new orphan changesets
694 694 $ echo "L" > L
695 695 $ hg add L
696 696 $ hg commit -m "L"
697 697 $ hg up '.^'
698 698 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
699 699 $ echo "M" > M
700 700 $ hg add M
701 701 $ hg commit --amend -m "M"
702 702 1 new orphan changesets
703 703 $ hg log -G
704 704 @ 18:bfaedf8eb73b M
705 705 |
706 706 | * 17:97219452e4bd L
707 707 | |
708 708 | x 16:fc37a630c901 K (rewritten using amend as 18:bfaedf8eb73b)
709 709 |/
710 710 | * 15:5ae8a643467b J
711 711 | |
712 712 | x 14:9ad579b4a5de I (rewritten using amend as 16:fc37a630c901)
713 713 |/
714 714 | * 12:acd174b7ab39 I
715 715 | |
716 716 | o 11:6c11a6218c97 H
717 717 | |
718 718 o | 10:b5313c85b22e D
719 719 |/
720 720 | * 8:53a6a128b2b7 M
721 721 | |\
722 722 | | x 7:02de42196ebe H (rewritten using rebase as 11:6c11a6218c97)
723 723 | | |
724 724 o---+ 6:eea13746799a G
725 725 | | |
726 726 | | o 5:24b6387c8c8c F
727 727 | | |
728 728 o---+ 4:9520eea781bc E
729 729 / /
730 730 x | 3:32af7686d403 D (rewritten using rebase as 10:b5313c85b22e)
731 731 | |
732 732 o | 2:5fddd98957c8 C
733 733 | |
734 734 o | 1:42ccdea3bb16 B
735 735 |/
736 736 o 0:cd010b8cd998 A
737 737
738 738 $ hg rebase -s 14 -d 17 --config experimental.rebaseskipobsolete=True
739 739 note: not rebasing 14:9ad579b4a5de "I", already in destination as 16:fc37a630c901 "K"
740 740 rebasing 15:5ae8a643467b "J"
741 741 1 new orphan changesets
742 742
743 743 $ cd ..
744 744
745 745 Skip obsolete changeset even with multiple hops
746 746 -----------------------------------------------
747 747
748 748 setup
749 749
750 750 $ hg init obsskip
751 751 $ cd obsskip
752 752 $ cat << EOF >> .hg/hgrc
753 753 > [experimental]
754 754 > rebaseskipobsolete = True
755 755 > [extensions]
756 756 > strip =
757 757 > EOF
758 758 $ echo A > A
759 759 $ hg add A
760 760 $ hg commit -m A
761 761 $ echo B > B
762 762 $ hg add B
763 763 $ hg commit -m B0
764 764 $ hg commit --amend -m B1
765 765 $ hg commit --amend -m B2
766 766 $ hg up --hidden 'desc(B0)'
767 767 updating to a hidden changeset a8b11f55fb19
768 768 (hidden revision 'a8b11f55fb19' was rewritten as: 261e70097290)
769 769 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
770 770 $ echo C > C
771 771 $ hg add C
772 772 $ hg commit -m C
773 773 1 new orphan changesets
774 774 $ hg log -G
775 775 @ 4:212cb178bcbb C
776 776 |
777 777 | o 3:261e70097290 B2
778 778 | |
779 779 x | 1:a8b11f55fb19 B0 (rewritten using amend as 3:261e70097290)
780 780 |/
781 781 o 0:4a2df7238c3b A
782 782
783 783
784 784 Rebase finds its way in a chain of marker
785 785
786 786 $ hg rebase -d 'desc(B2)'
787 787 note: not rebasing 1:a8b11f55fb19 "B0", already in destination as 3:261e70097290 "B2"
788 788 rebasing 4:212cb178bcbb "C" (tip)
789 789
790 790 Even when the chain include missing node
791 791
792 792 $ hg up --hidden 'desc(B0)'
793 793 updating to a hidden changeset a8b11f55fb19
794 794 (hidden revision 'a8b11f55fb19' was rewritten as: 261e70097290)
795 795 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
796 796 $ echo D > D
797 797 $ hg add D
798 798 $ hg commit -m D
799 799 1 new orphan changesets
800 800 $ hg --hidden strip -r 'desc(B1)'
801 801 saved backup bundle to $TESTTMP/obsskip/.hg/strip-backup/86f6414ccda7-b1c452ee-backup.hg
802 802 1 new orphan changesets
803 803 $ hg log -G
804 804 @ 5:1a79b7535141 D
805 805 |
806 806 | o 4:ff2c4d47b71d C
807 807 | |
808 808 | o 2:261e70097290 B2
809 809 | |
810 810 x | 1:a8b11f55fb19 B0 (rewritten using amend as 2:261e70097290)
811 811 |/
812 812 o 0:4a2df7238c3b A
813 813
814 814
815 815 $ hg rebase -d 'desc(B2)'
816 816 note: not rebasing 1:a8b11f55fb19 "B0", already in destination as 2:261e70097290 "B2"
817 817 rebasing 5:1a79b7535141 "D" (tip)
818 818 $ hg up 4
819 819 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
820 820 $ echo "O" > O
821 821 $ hg add O
822 822 $ hg commit -m O
823 823 $ echo "P" > P
824 824 $ hg add P
825 825 $ hg commit -m P
826 826 $ hg log -G
827 827 @ 8:8d47583e023f P
828 828 |
829 829 o 7:360bbaa7d3ce O
830 830 |
831 831 | o 6:9c48361117de D
832 832 | |
833 833 o | 4:ff2c4d47b71d C
834 834 |/
835 835 o 2:261e70097290 B2
836 836 |
837 837 o 0:4a2df7238c3b A
838 838
839 839 $ hg debugobsolete `hg log -r 7 -T '{node}\n'` --config experimental.evolution=true
840 840 obsoleted 1 changesets
841 841 1 new orphan changesets
842 842 $ hg rebase -d 6 -r "4::"
843 843 rebasing 4:ff2c4d47b71d "C"
844 844 note: not rebasing 7:360bbaa7d3ce "O", it has no successor
845 845 rebasing 8:8d47583e023f "P" (tip)
846 846
847 847 If all the changeset to be rebased are obsolete and present in the destination, we
848 848 should display a friendly error message
849 849
850 850 $ hg log -G
851 851 @ 10:121d9e3bc4c6 P
852 852 |
853 853 o 9:4be60e099a77 C
854 854 |
855 855 o 6:9c48361117de D
856 856 |
857 857 o 2:261e70097290 B2
858 858 |
859 859 o 0:4a2df7238c3b A
860 860
861 861
862 862 $ hg up 9
863 863 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
864 864 $ echo "non-relevant change" > nonrelevant
865 865 $ hg add nonrelevant
866 866 $ hg commit -m nonrelevant
867 867 created new head
868 868 $ hg debugobsolete `hg log -r 11 -T '{node}\n'` --config experimental.evolution=true
869 869 obsoleted 1 changesets
870 870 $ hg log -G
871 871 @ 11:f44da1f4954c nonrelevant (pruned)
872 872 |
873 873 | o 10:121d9e3bc4c6 P
874 874 |/
875 875 o 9:4be60e099a77 C
876 876 |
877 877 o 6:9c48361117de D
878 878 |
879 879 o 2:261e70097290 B2
880 880 |
881 881 o 0:4a2df7238c3b A
882 882
883 883 $ hg rebase -r . -d 10
884 884 note: not rebasing 11:f44da1f4954c "nonrelevant" (tip), it has no successor
885 885
886 886 If a rebase is going to create divergence, it should abort
887 887
888 888 $ hg log -G
889 889 @ 10:121d9e3bc4c6 P
890 890 |
891 891 o 9:4be60e099a77 C
892 892 |
893 893 o 6:9c48361117de D
894 894 |
895 895 o 2:261e70097290 B2
896 896 |
897 897 o 0:4a2df7238c3b A
898 898
899 899
900 900 $ hg up 9
901 901 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
902 902 $ echo "john" > doe
903 903 $ hg add doe
904 904 $ hg commit -m "john doe"
905 905 created new head
906 906 $ hg up 10
907 907 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
908 908 $ echo "foo" > bar
909 909 $ hg add bar
910 910 $ hg commit --amend -m "10'"
911 911 $ hg up 10 --hidden
912 912 updating to a hidden changeset 121d9e3bc4c6
913 913 (hidden revision '121d9e3bc4c6' was rewritten as: 77d874d096a2)
914 914 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
915 915 $ echo "bar" > foo
916 916 $ hg add foo
917 917 $ hg commit -m "bar foo"
918 918 1 new orphan changesets
919 919 $ hg log -G
920 920 @ 14:73568ab6879d bar foo
921 921 |
922 922 | o 13:77d874d096a2 10'
923 923 | |
924 924 | | o 12:3eb461388009 john doe
925 925 | |/
926 926 x | 10:121d9e3bc4c6 P (rewritten using amend as 13:77d874d096a2)
927 927 |/
928 928 o 9:4be60e099a77 C
929 929 |
930 930 o 6:9c48361117de D
931 931 |
932 932 o 2:261e70097290 B2
933 933 |
934 934 o 0:4a2df7238c3b A
935 935
936 936 $ hg summary
937 937 parent: 14:73568ab6879d tip (orphan)
938 938 bar foo
939 939 branch: default
940 940 commit: (clean)
941 941 update: 2 new changesets, 3 branch heads (merge)
942 942 phases: 8 draft
943 943 orphan: 1 changesets
944 944 $ hg rebase -s 10 -d 12
945 945 abort: this rebase will cause divergences from: 121d9e3bc4c6
946 946 (to force the rebase please set experimental.evolution.allowdivergence=True)
947 947 [255]
948 948 $ hg log -G
949 949 @ 14:73568ab6879d bar foo
950 950 |
951 951 | o 13:77d874d096a2 10'
952 952 | |
953 953 | | o 12:3eb461388009 john doe
954 954 | |/
955 955 x | 10:121d9e3bc4c6 P (rewritten using amend as 13:77d874d096a2)
956 956 |/
957 957 o 9:4be60e099a77 C
958 958 |
959 959 o 6:9c48361117de D
960 960 |
961 961 o 2:261e70097290 B2
962 962 |
963 963 o 0:4a2df7238c3b A
964 964
965 965 With experimental.evolution.allowdivergence=True, rebase can create divergence
966 966
967 967 $ hg rebase -s 10 -d 12 --config experimental.evolution.allowdivergence=True
968 968 rebasing 10:121d9e3bc4c6 "P"
969 969 rebasing 14:73568ab6879d "bar foo" (tip)
970 970 2 new content-divergent changesets
971 971 $ hg summary
972 972 parent: 16:61bd55f69bc4 tip
973 973 bar foo
974 974 branch: default
975 975 commit: (clean)
976 976 update: 1 new changesets, 2 branch heads (merge)
977 977 phases: 8 draft
978 978 content-divergent: 2 changesets
979 979
980 980 rebase --continue + skipped rev because their successors are in destination
981 981 we make a change in trunk and work on conflicting changes to make rebase abort.
982 982
983 983 $ hg log -G -r 16::
984 984 @ 16:61bd55f69bc4 bar foo
985 985 |
986 986 ~
987 987
988 988 Create the two changes in trunk
989 989 $ printf "a" > willconflict
990 990 $ hg add willconflict
991 991 $ hg commit -m "willconflict first version"
992 992
993 993 $ printf "dummy" > C
994 994 $ hg commit -m "dummy change successor"
995 995
996 996 Create the changes that we will rebase
997 997 $ hg update -C 16 -q
998 998 $ printf "b" > willconflict
999 999 $ hg add willconflict
1000 1000 $ hg commit -m "willconflict second version"
1001 1001 created new head
1002 1002 $ printf "dummy" > K
1003 1003 $ hg add K
1004 1004 $ hg commit -m "dummy change"
1005 1005 $ printf "dummy" > L
1006 1006 $ hg add L
1007 1007 $ hg commit -m "dummy change"
1008 1008 $ hg debugobsolete `hg log -r ".^" -T '{node}'` `hg log -r 18 -T '{node}'` --config experimental.evolution=true
1009 1009 obsoleted 1 changesets
1010 1010 1 new orphan changesets
1011 1011
1012 1012 $ hg log -G -r 16::
1013 1013 @ 21:7bdc8a87673d dummy change
1014 1014 |
1015 1015 x 20:8b31da3c4919 dummy change (rewritten as 18:601db7a18f51)
1016 1016 |
1017 1017 o 19:b82fb57ea638 willconflict second version
1018 1018 |
1019 1019 | o 18:601db7a18f51 dummy change successor
1020 1020 | |
1021 1021 | o 17:357ddf1602d5 willconflict first version
1022 1022 |/
1023 1023 o 16:61bd55f69bc4 bar foo
1024 1024 |
1025 1025 ~
1026 1026 $ hg rebase -r ".^^ + .^ + ." -d 18
1027 1027 rebasing 19:b82fb57ea638 "willconflict second version"
1028 1028 merging willconflict
1029 1029 warning: conflicts while merging willconflict! (edit, then use 'hg resolve --mark')
1030 1030 unresolved conflicts (see hg resolve, then hg rebase --continue)
1031 1031 [1]
1032 1032
1033 1033 $ hg resolve --mark willconflict
1034 1034 (no more unresolved files)
1035 1035 continue: hg rebase --continue
1036 1036 $ hg rebase --continue
1037 1037 rebasing 19:b82fb57ea638 "willconflict second version"
1038 1038 note: not rebasing 20:8b31da3c4919 "dummy change", already in destination as 18:601db7a18f51 "dummy change successor"
1039 1039 rebasing 21:7bdc8a87673d "dummy change" (tip)
1040 1040 $ cd ..
1041 1041
1042 1042 Divergence cases due to obsolete changesets
1043 1043 -------------------------------------------
1044 1044
1045 1045 We should ignore branches with unstable changesets when they are based on an
1046 1046 obsolete changeset which successor is in rebase set.
1047 1047
1048 1048 $ hg init divergence
1049 1049 $ cd divergence
1050 1050 $ cat >> .hg/hgrc << EOF
1051 1051 > [extensions]
1052 1052 > strip =
1053 1053 > [alias]
1054 1054 > strip = strip --no-backup --quiet
1055 1055 > [templates]
1056 1056 > instabilities = '{rev}:{node|short} {desc|firstline}{if(instabilities," ({instabilities})")}\n'
1057 1057 > EOF
1058 1058
1059 1059 $ hg debugdrawdag <<EOF
1060 1060 > e f
1061 1061 > | |
1062 1062 > d' d # replace: d -> d'
1063 1063 > \ /
1064 1064 > c
1065 1065 > |
1066 1066 > x b
1067 1067 > \|
1068 1068 > a
1069 1069 > EOF
1070 1070 1 new orphan changesets
1071 1071 $ hg log -G -r 'a'::
1072 1072 * 7:1143e9adc121 f
1073 1073 |
1074 1074 | o 6:d60ebfa0f1cb e
1075 1075 | |
1076 1076 | o 5:027ad6c5830d d'
1077 1077 | |
1078 1078 x | 4:76be324c128b d (rewritten using replace as 5:027ad6c5830d)
1079 1079 |/
1080 1080 o 3:a82ac2b38757 c
1081 1081 |
1082 1082 | o 2:630d7c95eff7 x
1083 1083 | |
1084 1084 o | 1:488e1b7e7341 b
1085 1085 |/
1086 1086 o 0:b173517d0057 a
1087 1087
1088 1088
1089 1089 Changeset d and its descendants are excluded to avoid divergence of d, which
1090 1090 would occur because the successor of d (d') is also in rebaseset. As a
1091 1091 consequence f (descendant of d) is left behind.
1092 1092
1093 1093 $ hg rebase -b 'e' -d 'x'
1094 1094 rebasing 1:488e1b7e7341 "b" (b)
1095 1095 rebasing 3:a82ac2b38757 "c" (c)
1096 1096 rebasing 5:027ad6c5830d "d'" (d')
1097 1097 rebasing 6:d60ebfa0f1cb "e" (e)
1098 1098 note: not rebasing 4:76be324c128b "d" (d) and its descendants as this would cause divergence
1099 1099 $ hg log -G -r 'a'::
1100 1100 o 11:eb6d63fc4ed5 e
1101 1101 |
1102 1102 o 10:44d8c724a70c d'
1103 1103 |
1104 1104 o 9:d008e6b4d3fd c
1105 1105 |
1106 1106 o 8:67e8f4a16c49 b
1107 1107 |
1108 1108 | * 7:1143e9adc121 f
1109 1109 | |
1110 1110 | | x 6:d60ebfa0f1cb e (rewritten using rebase as 11:eb6d63fc4ed5)
1111 1111 | | |
1112 1112 | | x 5:027ad6c5830d d' (rewritten using rebase as 10:44d8c724a70c)
1113 1113 | | |
1114 1114 | x | 4:76be324c128b d (rewritten using replace as 5:027ad6c5830d)
1115 1115 | |/
1116 1116 | x 3:a82ac2b38757 c (rewritten using rebase as 9:d008e6b4d3fd)
1117 1117 | |
1118 1118 o | 2:630d7c95eff7 x
1119 1119 | |
1120 1120 | x 1:488e1b7e7341 b (rewritten using rebase as 8:67e8f4a16c49)
1121 1121 |/
1122 1122 o 0:b173517d0057 a
1123 1123
1124 1124 $ hg strip -r 8:
1125 1125
1126 1126 If the rebase set has an obsolete (d) with a successor (d') outside the rebase
1127 1127 set and none in destination, we still get the divergence warning.
1128 1128 By allowing divergence, we can perform the rebase.
1129 1129
1130 1130 $ hg rebase -r 'c'::'f' -d 'x'
1131 1131 abort: this rebase will cause divergences from: 76be324c128b
1132 1132 (to force the rebase please set experimental.evolution.allowdivergence=True)
1133 1133 [255]
1134 1134 $ hg rebase --config experimental.evolution.allowdivergence=true -r 'c'::'f' -d 'x'
1135 1135 rebasing 3:a82ac2b38757 "c" (c)
1136 1136 rebasing 4:76be324c128b "d" (d)
1137 1137 rebasing 7:1143e9adc121 "f" (f tip)
1138 1138 1 new orphan changesets
1139 1139 2 new content-divergent changesets
1140 1140 $ hg log -G -r 'a':: -T instabilities
1141 1141 o 10:e1744ea07510 f
1142 1142 |
1143 1143 * 9:e2b36ea9a0a0 d (content-divergent)
1144 1144 |
1145 1145 o 8:6a0376de376e c
1146 1146 |
1147 1147 | x 7:1143e9adc121 f
1148 1148 | |
1149 1149 | | * 6:d60ebfa0f1cb e (orphan)
1150 1150 | | |
1151 1151 | | * 5:027ad6c5830d d' (orphan content-divergent)
1152 1152 | | |
1153 1153 | x | 4:76be324c128b d
1154 1154 | |/
1155 1155 | x 3:a82ac2b38757 c
1156 1156 | |
1157 1157 o | 2:630d7c95eff7 x
1158 1158 | |
1159 1159 | o 1:488e1b7e7341 b
1160 1160 |/
1161 1161 o 0:b173517d0057 a
1162 1162
1163 1163 $ hg strip -r 8:
1164 1164
1165 1165 (Not skipping obsoletes means that divergence is allowed.)
1166 1166
1167 1167 $ hg rebase --config experimental.rebaseskipobsolete=false -r 'c'::'f' -d 'x'
1168 1168 rebasing 3:a82ac2b38757 "c" (c)
1169 1169 rebasing 4:76be324c128b "d" (d)
1170 1170 rebasing 7:1143e9adc121 "f" (f tip)
1171 1171 1 new orphan changesets
1172 1172 2 new content-divergent changesets
1173 1173
1174 1174 $ hg strip -r 0:
1175 1175
1176 1176 Similar test on a more complex graph
1177 1177
1178 1178 $ hg debugdrawdag <<EOF
1179 1179 > g
1180 1180 > |
1181 1181 > f e
1182 1182 > | |
1183 1183 > e' d # replace: e -> e'
1184 1184 > \ /
1185 1185 > c
1186 1186 > |
1187 1187 > x b
1188 1188 > \|
1189 1189 > a
1190 1190 > EOF
1191 1191 1 new orphan changesets
1192 1192 $ hg log -G -r 'a':
1193 1193 * 8:2876ce66c6eb g
1194 1194 |
1195 1195 | o 7:3ffec603ab53 f
1196 1196 | |
1197 1197 x | 6:e36fae928aec e (rewritten using replace as 5:63324dc512ea)
1198 1198 | |
1199 1199 | o 5:63324dc512ea e'
1200 1200 | |
1201 1201 o | 4:76be324c128b d
1202 1202 |/
1203 1203 o 3:a82ac2b38757 c
1204 1204 |
1205 1205 | o 2:630d7c95eff7 x
1206 1206 | |
1207 1207 o | 1:488e1b7e7341 b
1208 1208 |/
1209 1209 o 0:b173517d0057 a
1210 1210
1211 1211 $ hg rebase -b 'f' -d 'x'
1212 1212 rebasing 1:488e1b7e7341 "b" (b)
1213 1213 rebasing 3:a82ac2b38757 "c" (c)
1214 1214 rebasing 5:63324dc512ea "e'" (e')
1215 1215 rebasing 7:3ffec603ab53 "f" (f)
1216 1216 rebasing 4:76be324c128b "d" (d)
1217 1217 note: not rebasing 6:e36fae928aec "e" (e) and its descendants as this would cause divergence
1218 1218 $ hg log -G -r 'a':
1219 1219 o 13:a1707a5b7c2c d
1220 1220 |
1221 1221 | o 12:ef6251596616 f
1222 1222 | |
1223 1223 | o 11:b6f172e64af9 e'
1224 1224 |/
1225 1225 o 10:d008e6b4d3fd c
1226 1226 |
1227 1227 o 9:67e8f4a16c49 b
1228 1228 |
1229 1229 | * 8:2876ce66c6eb g
1230 1230 | |
1231 1231 | | x 7:3ffec603ab53 f (rewritten using rebase as 12:ef6251596616)
1232 1232 | | |
1233 1233 | x | 6:e36fae928aec e (rewritten using replace as 5:63324dc512ea)
1234 1234 | | |
1235 1235 | | x 5:63324dc512ea e' (rewritten using rebase as 11:b6f172e64af9)
1236 1236 | | |
1237 1237 | x | 4:76be324c128b d (rewritten using rebase as 13:a1707a5b7c2c)
1238 1238 | |/
1239 1239 | x 3:a82ac2b38757 c (rewritten using rebase as 10:d008e6b4d3fd)
1240 1240 | |
1241 1241 o | 2:630d7c95eff7 x
1242 1242 | |
1243 1243 | x 1:488e1b7e7341 b (rewritten using rebase as 9:67e8f4a16c49)
1244 1244 |/
1245 1245 o 0:b173517d0057 a
1246 1246
1247 1247
1248 1248 issue5782
1249 1249 $ hg strip -r 0:
1250 1250 $ hg debugdrawdag <<EOF
1251 1251 > d
1252 1252 > |
1253 1253 > c1 c # replace: c -> c1
1254 1254 > \ /
1255 1255 > b
1256 1256 > |
1257 1257 > a
1258 1258 > EOF
1259 1259 1 new orphan changesets
1260 1260 $ hg debugobsolete `hg log -T "{node}" --hidden -r 'desc("c1")'`
1261 1261 obsoleted 1 changesets
1262 1262 $ hg log -G -r 'a': --hidden
1263 1263 * 4:76be324c128b d
1264 1264 |
1265 1265 | x 3:ef8a456de8fa c1 (pruned)
1266 1266 | |
1267 1267 x | 2:a82ac2b38757 c (rewritten using replace as 3:ef8a456de8fa)
1268 1268 |/
1269 1269 o 1:488e1b7e7341 b
1270 1270 |
1271 1271 o 0:b173517d0057 a
1272 1272
1273 1273 $ hg rebase -d 0 -r 2
1274 1274 rebasing 2:a82ac2b38757 "c" (c)
1275 1275 $ hg log -G -r 'a': --hidden
1276 1276 o 5:69ad416a4a26 c
1277 1277 |
1278 1278 | * 4:76be324c128b d
1279 1279 | |
1280 1280 | | x 3:ef8a456de8fa c1 (pruned)
1281 1281 | | |
1282 1282 | x | 2:a82ac2b38757 c (rewritten using replace as 3:ef8a456de8fa rewritten using rebase as 5:69ad416a4a26)
1283 1283 | |/
1284 1284 | o 1:488e1b7e7341 b
1285 1285 |/
1286 1286 o 0:b173517d0057 a
1287 1287
1288 1288 $ cd ..
1289 1289
1290 1290 Rebase merge where successor of one parent is equal to destination (issue5198)
1291 1291
1292 1292 $ hg init p1-succ-is-dest
1293 1293 $ cd p1-succ-is-dest
1294 1294
1295 1295 $ hg debugdrawdag <<EOF
1296 1296 > F
1297 1297 > /|
1298 1298 > E D B # replace: D -> B
1299 1299 > \|/
1300 1300 > A
1301 1301 > EOF
1302 1302 1 new orphan changesets
1303 1303
1304 1304 $ hg rebase -d B -s D
1305 1305 note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B" (B)
1306 1306 rebasing 4:66f1a38021c9 "F" (F tip)
1307 1307 $ hg log -G
1308 1308 o 5:50e9d60b99c6 F
1309 1309 |\
1310 1310 | | x 4:66f1a38021c9 F (rewritten using rebase as 5:50e9d60b99c6)
1311 1311 | |/|
1312 1312 | o | 3:7fb047a69f22 E
1313 1313 | | |
1314 1314 | | x 2:b18e25de2cf5 D (rewritten using replace as 1:112478962961)
1315 1315 | |/
1316 1316 o | 1:112478962961 B
1317 1317 |/
1318 1318 o 0:426bada5c675 A
1319 1319
1320 1320 $ cd ..
1321 1321
1322 1322 Rebase merge where successor of other parent is equal to destination
1323 1323
1324 1324 $ hg init p2-succ-is-dest
1325 1325 $ cd p2-succ-is-dest
1326 1326
1327 1327 $ hg debugdrawdag <<EOF
1328 1328 > F
1329 1329 > /|
1330 1330 > E D B # replace: E -> B
1331 1331 > \|/
1332 1332 > A
1333 1333 > EOF
1334 1334 1 new orphan changesets
1335 1335
1336 1336 $ hg rebase -d B -s E
1337 1337 note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B" (B)
1338 1338 rebasing 4:66f1a38021c9 "F" (F tip)
1339 1339 $ hg log -G
1340 1340 o 5:aae1787dacee F
1341 1341 |\
1342 1342 | | x 4:66f1a38021c9 F (rewritten using rebase as 5:aae1787dacee)
1343 1343 | |/|
1344 1344 | | x 3:7fb047a69f22 E (rewritten using replace as 1:112478962961)
1345 1345 | | |
1346 1346 | o | 2:b18e25de2cf5 D
1347 1347 | |/
1348 1348 o / 1:112478962961 B
1349 1349 |/
1350 1350 o 0:426bada5c675 A
1351 1351
1352 1352 $ cd ..
1353 1353
1354 1354 Rebase merge where successor of one parent is ancestor of destination
1355 1355
1356 1356 $ hg init p1-succ-in-dest
1357 1357 $ cd p1-succ-in-dest
1358 1358
1359 1359 $ hg debugdrawdag <<EOF
1360 1360 > F C
1361 1361 > /| |
1362 1362 > E D B # replace: D -> B
1363 1363 > \|/
1364 1364 > A
1365 1365 > EOF
1366 1366 1 new orphan changesets
1367 1367
1368 1368 $ hg rebase -d C -s D
1369 1369 note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B" (B)
1370 1370 rebasing 5:66f1a38021c9 "F" (F tip)
1371 1371
1372 1372 $ hg log -G
1373 1373 o 6:0913febf6439 F
1374 1374 |\
1375 1375 +---x 5:66f1a38021c9 F (rewritten using rebase as 6:0913febf6439)
1376 1376 | | |
1377 1377 | o | 4:26805aba1e60 C
1378 1378 | | |
1379 1379 o | | 3:7fb047a69f22 E
1380 1380 | | |
1381 1381 +---x 2:b18e25de2cf5 D (rewritten using replace as 1:112478962961)
1382 1382 | |
1383 1383 | o 1:112478962961 B
1384 1384 |/
1385 1385 o 0:426bada5c675 A
1386 1386
1387 1387 $ cd ..
1388 1388
1389 1389 Rebase merge where successor of other parent is ancestor of destination
1390 1390
1391 1391 $ hg init p2-succ-in-dest
1392 1392 $ cd p2-succ-in-dest
1393 1393
1394 1394 $ hg debugdrawdag <<EOF
1395 1395 > F C
1396 1396 > /| |
1397 1397 > E D B # replace: E -> B
1398 1398 > \|/
1399 1399 > A
1400 1400 > EOF
1401 1401 1 new orphan changesets
1402 1402
1403 1403 $ hg rebase -d C -s E
1404 1404 note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B" (B)
1405 1405 rebasing 5:66f1a38021c9 "F" (F tip)
1406 1406 $ hg log -G
1407 1407 o 6:c6ab0cc6d220 F
1408 1408 |\
1409 1409 +---x 5:66f1a38021c9 F (rewritten using rebase as 6:c6ab0cc6d220)
1410 1410 | | |
1411 1411 | o | 4:26805aba1e60 C
1412 1412 | | |
1413 1413 | | x 3:7fb047a69f22 E (rewritten using replace as 1:112478962961)
1414 1414 | | |
1415 1415 o---+ 2:b18e25de2cf5 D
1416 1416 / /
1417 1417 o / 1:112478962961 B
1418 1418 |/
1419 1419 o 0:426bada5c675 A
1420 1420
1421 1421 $ cd ..
1422 1422
1423 1423 Rebase merge where successor of one parent is ancestor of destination
1424 1424
1425 1425 $ hg init p1-succ-in-dest-b
1426 1426 $ cd p1-succ-in-dest-b
1427 1427
1428 1428 $ hg debugdrawdag <<EOF
1429 1429 > F C
1430 1430 > /| |
1431 1431 > E D B # replace: E -> B
1432 1432 > \|/
1433 1433 > A
1434 1434 > EOF
1435 1435 1 new orphan changesets
1436 1436
1437 1437 $ hg rebase -d C -b F
1438 1438 rebasing 2:b18e25de2cf5 "D" (D)
1439 1439 note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B" (B)
1440 1440 rebasing 5:66f1a38021c9 "F" (F tip)
1441 1441 note: rebase of 5:66f1a38021c9 created no changes to commit
1442 1442 $ hg log -G
1443 1443 o 6:8f47515dda15 D
1444 1444 |
1445 1445 | x 5:66f1a38021c9 F (pruned using rebase)
1446 1446 | |\
1447 1447 o | | 4:26805aba1e60 C
1448 1448 | | |
1449 1449 | | x 3:7fb047a69f22 E (rewritten using replace as 1:112478962961)
1450 1450 | | |
1451 1451 | x | 2:b18e25de2cf5 D (rewritten using rebase as 6:8f47515dda15)
1452 1452 | |/
1453 1453 o / 1:112478962961 B
1454 1454 |/
1455 1455 o 0:426bada5c675 A
1456 1456
1457 1457 $ cd ..
1458 1458
1459 1459 Rebase merge where successor of other parent is ancestor of destination
1460 1460
1461 1461 $ hg init p2-succ-in-dest-b
1462 1462 $ cd p2-succ-in-dest-b
1463 1463
1464 1464 $ hg debugdrawdag <<EOF
1465 1465 > F C
1466 1466 > /| |
1467 1467 > E D B # replace: D -> B
1468 1468 > \|/
1469 1469 > A
1470 1470 > EOF
1471 1471 1 new orphan changesets
1472 1472
1473 1473 $ hg rebase -d C -b F
1474 1474 note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B" (B)
1475 1475 rebasing 3:7fb047a69f22 "E" (E)
1476 1476 rebasing 5:66f1a38021c9 "F" (F tip)
1477 1477 note: rebase of 5:66f1a38021c9 created no changes to commit
1478 1478
1479 1479 $ hg log -G
1480 1480 o 6:533690786a86 E
1481 1481 |
1482 1482 | x 5:66f1a38021c9 F (pruned using rebase)
1483 1483 | |\
1484 1484 o | | 4:26805aba1e60 C
1485 1485 | | |
1486 1486 | | x 3:7fb047a69f22 E (rewritten using rebase as 6:533690786a86)
1487 1487 | | |
1488 1488 | x | 2:b18e25de2cf5 D (rewritten using replace as 1:112478962961)
1489 1489 | |/
1490 1490 o / 1:112478962961 B
1491 1491 |/
1492 1492 o 0:426bada5c675 A
1493 1493
1494 1494 $ cd ..
1495 1495
1496 $ hg init p2-succ-in-dest-c
1497 $ cd p2-succ-in-dest-c
1498
1499 The scenario here was that B::D were developed on default. B was queued on
1500 stable, but amended before being push to hg-committed. C was queued on default,
1501 along with unrelated J.
1502
1503 $ hg debugdrawdag <<EOF
1504 > J
1505 > |
1506 > F
1507 > |
1508 > E
1509 > | D
1510 > | |
1511 > | C # replace: C -> F
1512 > | | H I # replace: B -> H -> I
1513 > | B |/
1514 > |/ G
1515 > A
1516 > EOF
1517 1 new orphan changesets
1518
1519 This strip seems to be the key to avoid an early divergence warning.
1520 $ hg --config extensions.strip= --hidden strip -qr H
1521 1 new orphan changesets
1522
1523 $ hg rebase -b 'desc("D")' -d 'desc("J")'
1524 abort: this rebase will cause divergences from: 112478962961
1525 (to force the rebase please set experimental.evolution.allowdivergence=True)
1526 [255]
1527
1496 1528 Rebase merge where both parents have successors in destination
1497 1529
1498 1530 $ hg init p12-succ-in-dest
1499 1531 $ cd p12-succ-in-dest
1500 1532 $ hg debugdrawdag <<'EOS'
1501 1533 > E F
1502 1534 > /| /| # replace: A -> C
1503 1535 > A B C D # replace: B -> D
1504 1536 > | |
1505 1537 > X Y
1506 1538 > EOS
1507 1539 1 new orphan changesets
1508 1540 $ hg rebase -r A+B+E -d F
1509 1541 note: not rebasing 4:a3d17304151f "A" (A), already in destination as 0:96cc3511f894 "C" (C)
1510 1542 note: not rebasing 5:b23a2cc00842 "B" (B), already in destination as 1:058c1e1fb10a "D" (D)
1511 1543 rebasing 7:dac5d11c5a7d "E" (E tip)
1512 1544 abort: rebasing 7:dac5d11c5a7d will include unwanted changes from 3:59c792af609c, 5:b23a2cc00842 or 2:ba2b7fa7166d, 4:a3d17304151f
1513 1545 [255]
1514 1546 $ cd ..
1515 1547
1516 1548 Rebase a non-clean merge. One parent has successor in destination, the other
1517 1549 parent moves as requested.
1518 1550
1519 1551 $ hg init p1-succ-p2-move
1520 1552 $ cd p1-succ-p2-move
1521 1553 $ hg debugdrawdag <<'EOS'
1522 1554 > D Z
1523 1555 > /| | # replace: A -> C
1524 1556 > A B C # D/D = D
1525 1557 > EOS
1526 1558 1 new orphan changesets
1527 1559 $ hg rebase -r A+B+D -d Z
1528 1560 note: not rebasing 0:426bada5c675 "A" (A), already in destination as 2:96cc3511f894 "C" (C)
1529 1561 rebasing 1:fc2b737bb2e5 "B" (B)
1530 1562 rebasing 3:b8ed089c80ad "D" (D)
1531 1563
1532 1564 $ rm .hg/localtags
1533 1565 $ hg log -G
1534 1566 o 6:e4f78693cc88 D
1535 1567 |
1536 1568 o 5:76840d832e98 B
1537 1569 |
1538 1570 o 4:50e41c1f3950 Z
1539 1571 |
1540 1572 o 2:96cc3511f894 C
1541 1573
1542 1574 $ hg files -r tip
1543 1575 B
1544 1576 C
1545 1577 D
1546 1578 Z
1547 1579
1548 1580 $ cd ..
1549 1581
1550 1582 $ hg init p1-move-p2-succ
1551 1583 $ cd p1-move-p2-succ
1552 1584 $ hg debugdrawdag <<'EOS'
1553 1585 > D Z
1554 1586 > /| | # replace: B -> C
1555 1587 > A B C # D/D = D
1556 1588 > EOS
1557 1589 1 new orphan changesets
1558 1590 $ hg rebase -r B+A+D -d Z
1559 1591 rebasing 0:426bada5c675 "A" (A)
1560 1592 note: not rebasing 1:fc2b737bb2e5 "B" (B), already in destination as 2:96cc3511f894 "C" (C)
1561 1593 rebasing 3:b8ed089c80ad "D" (D)
1562 1594
1563 1595 $ rm .hg/localtags
1564 1596 $ hg log -G
1565 1597 o 6:1b355ed94d82 D
1566 1598 |
1567 1599 o 5:a81a74d764a6 A
1568 1600 |
1569 1601 o 4:50e41c1f3950 Z
1570 1602 |
1571 1603 o 2:96cc3511f894 C
1572 1604
1573 1605 $ hg files -r tip
1574 1606 A
1575 1607 C
1576 1608 D
1577 1609 Z
1578 1610
1579 1611 $ cd ..
1580 1612
1581 1613 Test that bookmark is moved and working dir is updated when all changesets have
1582 1614 equivalents in destination
1583 1615 $ hg init rbsrepo && cd rbsrepo
1584 1616 $ echo "[experimental]" > .hg/hgrc
1585 1617 $ echo "evolution=true" >> .hg/hgrc
1586 1618 $ echo "rebaseskipobsolete=on" >> .hg/hgrc
1587 1619 $ echo root > root && hg ci -Am root
1588 1620 adding root
1589 1621 $ echo a > a && hg ci -Am a
1590 1622 adding a
1591 1623 $ hg up 0
1592 1624 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1593 1625 $ echo b > b && hg ci -Am b
1594 1626 adding b
1595 1627 created new head
1596 1628 $ hg rebase -r 2 -d 1
1597 1629 rebasing 2:1e9a3c00cbe9 "b" (tip)
1598 1630 $ hg log -r . # working dir is at rev 3 (successor of 2)
1599 1631 3:be1832deae9a b (no-eol)
1600 1632 $ hg book -r 2 mybook --hidden # rev 2 has a bookmark on it now
1601 1633 bookmarking hidden changeset 1e9a3c00cbe9
1602 1634 (hidden revision '1e9a3c00cbe9' was rewritten as: be1832deae9a)
1603 1635 $ hg up 2 && hg log -r . # working dir is at rev 2 again
1604 1636 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1605 1637 2:1e9a3c00cbe9 b (rewritten using rebase as 3:be1832deae9a) (no-eol)
1606 1638 $ hg rebase -r 2 -d 3 --config experimental.evolution.track-operation=1
1607 1639 note: not rebasing 2:1e9a3c00cbe9 "b" (mybook), already in destination as 3:be1832deae9a "b" (tip)
1608 1640 Check that working directory and bookmark was updated to rev 3 although rev 2
1609 1641 was skipped
1610 1642 $ hg log -r .
1611 1643 3:be1832deae9a b (no-eol)
1612 1644 $ hg bookmarks
1613 1645 mybook 3:be1832deae9a
1614 1646 $ hg debugobsolete --rev tip
1615 1647 1e9a3c00cbe90d236ac05ef61efcc5e40b7412bc be1832deae9ac531caa7438b8dcf6055a122cd8e 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
1616 1648
1617 1649 Obsoleted working parent and bookmark could be moved if an ancestor of working
1618 1650 parent gets moved:
1619 1651
1620 1652 $ hg init $TESTTMP/ancestor-wd-move
1621 1653 $ cd $TESTTMP/ancestor-wd-move
1622 1654 $ hg debugdrawdag <<'EOS'
1623 1655 > E D1 # rebase: D1 -> D2
1624 1656 > | |
1625 1657 > | C
1626 1658 > D2 |
1627 1659 > | B
1628 1660 > |/
1629 1661 > A
1630 1662 > EOS
1631 1663 $ hg update D1 -q
1632 1664 $ hg bookmark book -i
1633 1665 $ hg rebase -r B+D1 -d E
1634 1666 rebasing 1:112478962961 "B" (B)
1635 1667 note: not rebasing 5:15ecf15e0114 "D1" (book D1 tip), already in destination as 2:0807738e0be9 "D2" (D2)
1636 1668 1 new orphan changesets
1637 1669 $ hg log -G -T '{desc} {bookmarks}'
1638 1670 @ B book
1639 1671 |
1640 1672 | x D1
1641 1673 | |
1642 1674 o | E
1643 1675 | |
1644 1676 | * C
1645 1677 | |
1646 1678 o | D2
1647 1679 | |
1648 1680 | x B
1649 1681 |/
1650 1682 o A
1651 1683
1652 1684 Rebasing a merge with one of its parent having a hidden successor
1653 1685
1654 1686 $ hg init $TESTTMP/merge-p1-hidden-successor
1655 1687 $ cd $TESTTMP/merge-p1-hidden-successor
1656 1688
1657 1689 $ hg debugdrawdag <<'EOS'
1658 1690 > E
1659 1691 > |
1660 1692 > B3 B2 # amend: B1 -> B2 -> B3
1661 1693 > |/ # B2 is hidden
1662 1694 > | D
1663 1695 > | |\
1664 1696 > | B1 C
1665 1697 > |/
1666 1698 > A
1667 1699 > EOS
1668 1700 1 new orphan changesets
1669 1701
1670 1702 $ eval `hg tags -T '{tag}={node}\n'`
1671 1703 $ rm .hg/localtags
1672 1704
1673 1705 $ hg rebase -r $D -d $E
1674 1706 rebasing 5:9e62094e4d94 "D"
1675 1707
1676 1708 $ hg log -G
1677 1709 o 7:a699d059adcf D
1678 1710 |\
1679 1711 | o 6:ecc93090a95c E
1680 1712 | |
1681 1713 | o 4:0dc878468a23 B3
1682 1714 | |
1683 1715 o | 1:96cc3511f894 C
1684 1716 /
1685 1717 o 0:426bada5c675 A
1686 1718
1687 1719 For some reasons (--hidden, rebaseskipobsolete=0, directaccess, etc.),
1688 1720 rebasestate may contain hidden hashes. "rebase --abort" should work regardless.
1689 1721
1690 1722 $ hg init $TESTTMP/hidden-state1
1691 1723 $ cd $TESTTMP/hidden-state1
1692 1724 $ cat >> .hg/hgrc <<EOF
1693 1725 > [experimental]
1694 1726 > rebaseskipobsolete=0
1695 1727 > EOF
1696 1728
1697 1729 $ hg debugdrawdag <<'EOS'
1698 1730 > C
1699 1731 > |
1700 1732 > D B # prune: B, C
1701 1733 > |/ # B/D=B
1702 1734 > A
1703 1735 > EOS
1704 1736
1705 1737 $ eval `hg tags -T '{tag}={node}\n'`
1706 1738 $ rm .hg/localtags
1707 1739
1708 1740 $ hg update -q $C --hidden
1709 1741 updating to a hidden changeset 7829726be4dc
1710 1742 (hidden revision '7829726be4dc' is pruned)
1711 1743 $ hg rebase -s $B -d $D
1712 1744 rebasing 1:2ec65233581b "B"
1713 1745 merging D
1714 1746 warning: conflicts while merging D! (edit, then use 'hg resolve --mark')
1715 1747 unresolved conflicts (see hg resolve, then hg rebase --continue)
1716 1748 [1]
1717 1749
1718 1750 $ cp -R . $TESTTMP/hidden-state2
1719 1751
1720 1752 $ hg log -G
1721 1753 @ 2:b18e25de2cf5 D
1722 1754 |
1723 1755 | @ 1:2ec65233581b B (pruned using prune)
1724 1756 |/
1725 1757 o 0:426bada5c675 A
1726 1758
1727 1759 $ hg summary
1728 1760 parent: 2:b18e25de2cf5 tip
1729 1761 D
1730 1762 parent: 1:2ec65233581b (obsolete)
1731 1763 B
1732 1764 branch: default
1733 1765 commit: 2 modified, 1 unknown, 1 unresolved (merge)
1734 1766 update: (current)
1735 1767 phases: 3 draft
1736 1768 rebase: 0 rebased, 2 remaining (rebase --continue)
1737 1769
1738 1770 $ hg rebase --abort
1739 1771 rebase aborted
1740 1772
1741 1773 Also test --continue for the above case
1742 1774
1743 1775 $ cd $TESTTMP/hidden-state2
1744 1776 $ hg resolve -m
1745 1777 (no more unresolved files)
1746 1778 continue: hg rebase --continue
1747 1779 $ hg rebase --continue
1748 1780 rebasing 1:2ec65233581b "B"
1749 1781 rebasing 3:7829726be4dc "C" (tip)
1750 1782 $ hg log -G
1751 1783 @ 5:1964d5d5b547 C
1752 1784 |
1753 1785 o 4:68deb90c12a2 B
1754 1786 |
1755 1787 o 2:b18e25de2cf5 D
1756 1788 |
1757 1789 o 0:426bada5c675 A
1758 1790
General Comments 0
You need to be logged in to leave comments. Login now