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