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