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