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