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