##// END OF EJS Templates
rebase: use cmdutil.check_at_most_one_arg() for -b/-s/-r...
Martin von Zweigbergk -
r44381:08b8bf4a default
parent child Browse files
Show More
@@ -1,2306 +1,2301
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 1027 cmdutil.check_at_most_one_arg(opts, b'confirm', b'dry_run')
1028 cmdutil.check_at_most_one_arg(opts, b'rev', b'source', b'base')
1028 1029
1029 1030 if action or repo.currenttransaction() is not None:
1030 1031 # in-memory rebase is not compatible with resuming rebases.
1031 1032 # (Or if it is run within a transaction, since the restart logic can
1032 1033 # fail the entire transaction.)
1033 1034 inmemory = False
1034 1035
1035 1036 if opts.get(b'auto_orphans'):
1036 1037 for key in opts:
1037 1038 if key != b'auto_orphans' and opts.get(key):
1038 1039 raise error.Abort(
1039 1040 _(b'--auto-orphans is incompatible with %s') % (b'--' + key)
1040 1041 )
1041 1042 userrevs = list(repo.revs(opts.get(b'auto_orphans')))
1042 1043 opts[b'rev'] = [revsetlang.formatspec(b'%ld and orphan()', userrevs)]
1043 1044 opts[b'dest'] = b'_destautoorphanrebase(SRC)'
1044 1045
1045 1046 if opts.get(b'dry_run') or opts.get(b'confirm'):
1046 1047 return _dryrunrebase(ui, repo, action, opts)
1047 1048 elif action == b'stop':
1048 1049 rbsrt = rebaseruntime(repo, ui)
1049 1050 with repo.wlock(), repo.lock():
1050 1051 rbsrt.restorestatus()
1051 1052 if rbsrt.collapsef:
1052 1053 raise error.Abort(_(b"cannot stop in --collapse session"))
1053 1054 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1054 1055 if not (rbsrt.keepf or allowunstable):
1055 1056 raise error.Abort(
1056 1057 _(
1057 1058 b"cannot remove original changesets with"
1058 1059 b" unrebased descendants"
1059 1060 ),
1060 1061 hint=_(
1061 1062 b'either enable obsmarkers to allow unstable '
1062 1063 b'revisions or use --keep to keep original '
1063 1064 b'changesets'
1064 1065 ),
1065 1066 )
1066 1067 if needupdate(repo, rbsrt.state):
1067 1068 # update to the current working revision
1068 1069 # to clear interrupted merge
1069 1070 hg.updaterepo(repo, rbsrt.originalwd, overwrite=True)
1070 1071 rbsrt._finishrebase()
1071 1072 return 0
1072 1073 elif inmemory:
1073 1074 try:
1074 1075 # in-memory merge doesn't support conflicts, so if we hit any, abort
1075 1076 # and re-run as an on-disk merge.
1076 1077 overrides = {(b'rebase', b'singletransaction'): True}
1077 1078 with ui.configoverride(overrides, b'rebase'):
1078 1079 return _dorebase(ui, repo, action, opts, inmemory=inmemory)
1079 1080 except error.InMemoryMergeConflictsError:
1080 1081 ui.warn(
1081 1082 _(
1082 1083 b'hit merge conflicts; re-running rebase without in-memory'
1083 1084 b' merge\n'
1084 1085 )
1085 1086 )
1086 1087 # TODO: Make in-memory merge not use the on-disk merge state, so
1087 1088 # we don't have to clean it here
1088 1089 mergemod.mergestate.clean(repo)
1089 1090 clearstatus(repo)
1090 1091 clearcollapsemsg(repo)
1091 1092 return _dorebase(ui, repo, action, opts, inmemory=False)
1092 1093 else:
1093 1094 return _dorebase(ui, repo, action, opts)
1094 1095
1095 1096
1096 1097 def _dryrunrebase(ui, repo, action, opts):
1097 1098 rbsrt = rebaseruntime(repo, ui, inmemory=True, opts=opts)
1098 1099 confirm = opts.get(b'confirm')
1099 1100 if confirm:
1100 1101 ui.status(_(b'starting in-memory rebase\n'))
1101 1102 else:
1102 1103 ui.status(
1103 1104 _(b'starting dry-run rebase; repository will not be changed\n')
1104 1105 )
1105 1106 with repo.wlock(), repo.lock():
1106 1107 needsabort = True
1107 1108 try:
1108 1109 overrides = {(b'rebase', b'singletransaction'): True}
1109 1110 with ui.configoverride(overrides, b'rebase'):
1110 1111 _origrebase(
1111 1112 ui,
1112 1113 repo,
1113 1114 action,
1114 1115 opts,
1115 1116 rbsrt,
1116 1117 inmemory=True,
1117 1118 leaveunfinished=True,
1118 1119 )
1119 1120 except error.InMemoryMergeConflictsError:
1120 1121 ui.status(_(b'hit a merge conflict\n'))
1121 1122 return 1
1122 1123 except error.Abort:
1123 1124 needsabort = False
1124 1125 raise
1125 1126 else:
1126 1127 if confirm:
1127 1128 ui.status(_(b'rebase completed successfully\n'))
1128 1129 if not ui.promptchoice(_(b'apply changes (yn)?$$ &Yes $$ &No')):
1129 1130 # finish unfinished rebase
1130 1131 rbsrt._finishrebase()
1131 1132 else:
1132 1133 rbsrt._prepareabortorcontinue(
1133 1134 isabort=True, backup=False, suppwarns=True
1134 1135 )
1135 1136 needsabort = False
1136 1137 else:
1137 1138 ui.status(
1138 1139 _(
1139 1140 b'dry-run rebase completed successfully; run without'
1140 1141 b' -n/--dry-run to perform this rebase\n'
1141 1142 )
1142 1143 )
1143 1144 return 0
1144 1145 finally:
1145 1146 if needsabort:
1146 1147 # no need to store backup in case of dryrun
1147 1148 rbsrt._prepareabortorcontinue(
1148 1149 isabort=True, backup=False, suppwarns=True
1149 1150 )
1150 1151
1151 1152
1152 1153 def _dorebase(ui, repo, action, opts, inmemory=False):
1153 1154 rbsrt = rebaseruntime(repo, ui, inmemory, opts)
1154 1155 return _origrebase(ui, repo, action, opts, rbsrt, inmemory=inmemory)
1155 1156
1156 1157
1157 1158 def _origrebase(
1158 1159 ui, repo, action, opts, rbsrt, inmemory=False, leaveunfinished=False
1159 1160 ):
1160 1161 assert action != b'stop'
1161 1162 with repo.wlock(), repo.lock():
1162 1163 # Validate input and define rebasing points
1163 1164 destf = opts.get(b'dest', None)
1164 1165 srcf = opts.get(b'source', None)
1165 1166 basef = opts.get(b'base', None)
1166 1167 revf = opts.get(b'rev', [])
1167 1168 # search default destination in this space
1168 1169 # used in the 'hg pull --rebase' case, see issue 5214.
1169 1170 destspace = opts.get(b'_destspace')
1170 1171 if opts.get(b'interactive'):
1171 1172 try:
1172 1173 if extensions.find(b'histedit'):
1173 1174 enablehistedit = b''
1174 1175 except KeyError:
1175 1176 enablehistedit = b" --config extensions.histedit="
1176 1177 help = b"hg%s help -e histedit" % enablehistedit
1177 1178 msg = (
1178 1179 _(
1179 1180 b"interactive history editing is supported by the "
1180 1181 b"'histedit' extension (see \"%s\")"
1181 1182 )
1182 1183 % help
1183 1184 )
1184 1185 raise error.Abort(msg)
1185 1186
1186 1187 if rbsrt.collapsemsg and not rbsrt.collapsef:
1187 1188 raise error.Abort(_(b'message can only be specified with collapse'))
1188 1189
1189 1190 if action:
1190 1191 if rbsrt.collapsef:
1191 1192 raise error.Abort(
1192 1193 _(b'cannot use collapse with continue or abort')
1193 1194 )
1194 1195 if srcf or basef or destf:
1195 1196 raise error.Abort(
1196 1197 _(b'abort and continue do not allow specifying revisions')
1197 1198 )
1198 1199 if action == b'abort' and opts.get(b'tool', False):
1199 1200 ui.warn(_(b'tool option will be ignored\n'))
1200 1201 if action == b'continue':
1201 1202 ms = mergemod.mergestate.read(repo)
1202 1203 mergeutil.checkunresolved(ms)
1203 1204
1204 1205 retcode = rbsrt._prepareabortorcontinue(
1205 1206 isabort=(action == b'abort')
1206 1207 )
1207 1208 if retcode is not None:
1208 1209 return retcode
1209 1210 else:
1210 1211 destmap = _definedestmap(
1211 1212 ui,
1212 1213 repo,
1213 1214 inmemory,
1214 1215 destf,
1215 1216 srcf,
1216 1217 basef,
1217 1218 revf,
1218 1219 destspace=destspace,
1219 1220 )
1220 1221 retcode = rbsrt._preparenewrebase(destmap)
1221 1222 if retcode is not None:
1222 1223 return retcode
1223 1224 storecollapsemsg(repo, rbsrt.collapsemsg)
1224 1225
1225 1226 tr = None
1226 1227
1227 1228 singletr = ui.configbool(b'rebase', b'singletransaction')
1228 1229 if singletr:
1229 1230 tr = repo.transaction(b'rebase')
1230 1231
1231 1232 # If `rebase.singletransaction` is enabled, wrap the entire operation in
1232 1233 # one transaction here. Otherwise, transactions are obtained when
1233 1234 # committing each node, which is slower but allows partial success.
1234 1235 with util.acceptintervention(tr):
1235 1236 # Same logic for the dirstate guard, except we don't create one when
1236 1237 # rebasing in-memory (it's not needed).
1237 1238 dsguard = None
1238 1239 if singletr and not inmemory:
1239 1240 dsguard = dirstateguard.dirstateguard(repo, b'rebase')
1240 1241 with util.acceptintervention(dsguard):
1241 1242 rbsrt._performrebase(tr)
1242 1243 if not leaveunfinished:
1243 1244 rbsrt._finishrebase()
1244 1245
1245 1246
1246 1247 def _definedestmap(
1247 1248 ui,
1248 1249 repo,
1249 1250 inmemory,
1250 1251 destf=None,
1251 1252 srcf=None,
1252 1253 basef=None,
1253 1254 revf=None,
1254 1255 destspace=None,
1255 1256 ):
1256 1257 """use revisions argument to define destmap {srcrev: destrev}"""
1257 1258 if revf is None:
1258 1259 revf = []
1259 1260
1260 1261 # destspace is here to work around issues with `hg pull --rebase` see
1261 1262 # issue5214 for details
1262 if srcf and basef:
1263 raise error.Abort(_(b'cannot specify both a source and a base'))
1264 if revf and basef:
1265 raise error.Abort(_(b'cannot specify both a revision and a base'))
1266 if revf and srcf:
1267 raise error.Abort(_(b'cannot specify both a revision and a source'))
1268 1263
1269 1264 cmdutil.checkunfinished(repo)
1270 1265 if not inmemory:
1271 1266 cmdutil.bailifchanged(repo)
1272 1267
1273 1268 if ui.configbool(b'commands', b'rebase.requiredest') and not destf:
1274 1269 raise error.Abort(
1275 1270 _(b'you must specify a destination'),
1276 1271 hint=_(b'use: hg rebase -d REV'),
1277 1272 )
1278 1273
1279 1274 dest = None
1280 1275
1281 1276 if revf:
1282 1277 rebaseset = scmutil.revrange(repo, revf)
1283 1278 if not rebaseset:
1284 1279 ui.status(_(b'empty "rev" revision set - nothing to rebase\n'))
1285 1280 return None
1286 1281 elif srcf:
1287 1282 src = scmutil.revrange(repo, [srcf])
1288 1283 if not src:
1289 1284 ui.status(_(b'empty "source" revision set - nothing to rebase\n'))
1290 1285 return None
1291 1286 rebaseset = repo.revs(b'(%ld)::', src)
1292 1287 assert rebaseset
1293 1288 else:
1294 1289 base = scmutil.revrange(repo, [basef or b'.'])
1295 1290 if not base:
1296 1291 ui.status(
1297 1292 _(b'empty "base" revision set - ' b"can't compute rebase set\n")
1298 1293 )
1299 1294 return None
1300 1295 if destf:
1301 1296 # --base does not support multiple destinations
1302 1297 dest = scmutil.revsingle(repo, destf)
1303 1298 else:
1304 1299 dest = repo[_destrebase(repo, base, destspace=destspace)]
1305 1300 destf = bytes(dest)
1306 1301
1307 1302 roots = [] # selected children of branching points
1308 1303 bpbase = {} # {branchingpoint: [origbase]}
1309 1304 for b in base: # group bases by branching points
1310 1305 bp = repo.revs(b'ancestor(%d, %d)', b, dest.rev()).first()
1311 1306 bpbase[bp] = bpbase.get(bp, []) + [b]
1312 1307 if None in bpbase:
1313 1308 # emulate the old behavior, showing "nothing to rebase" (a better
1314 1309 # behavior may be abort with "cannot find branching point" error)
1315 1310 bpbase.clear()
1316 1311 for bp, bs in pycompat.iteritems(bpbase): # calculate roots
1317 1312 roots += list(repo.revs(b'children(%d) & ancestors(%ld)', bp, bs))
1318 1313
1319 1314 rebaseset = repo.revs(b'%ld::', roots)
1320 1315
1321 1316 if not rebaseset:
1322 1317 # transform to list because smartsets are not comparable to
1323 1318 # lists. This should be improved to honor laziness of
1324 1319 # smartset.
1325 1320 if list(base) == [dest.rev()]:
1326 1321 if basef:
1327 1322 ui.status(
1328 1323 _(
1329 1324 b'nothing to rebase - %s is both "base"'
1330 1325 b' and destination\n'
1331 1326 )
1332 1327 % dest
1333 1328 )
1334 1329 else:
1335 1330 ui.status(
1336 1331 _(
1337 1332 b'nothing to rebase - working directory '
1338 1333 b'parent is also destination\n'
1339 1334 )
1340 1335 )
1341 1336 elif not repo.revs(b'%ld - ::%d', base, dest.rev()):
1342 1337 if basef:
1343 1338 ui.status(
1344 1339 _(
1345 1340 b'nothing to rebase - "base" %s is '
1346 1341 b'already an ancestor of destination '
1347 1342 b'%s\n'
1348 1343 )
1349 1344 % (b'+'.join(bytes(repo[r]) for r in base), dest)
1350 1345 )
1351 1346 else:
1352 1347 ui.status(
1353 1348 _(
1354 1349 b'nothing to rebase - working '
1355 1350 b'directory parent is already an '
1356 1351 b'ancestor of destination %s\n'
1357 1352 )
1358 1353 % dest
1359 1354 )
1360 1355 else: # can it happen?
1361 1356 ui.status(
1362 1357 _(b'nothing to rebase from %s to %s\n')
1363 1358 % (b'+'.join(bytes(repo[r]) for r in base), dest)
1364 1359 )
1365 1360 return None
1366 1361
1367 1362 rebasingwcp = repo[b'.'].rev() in rebaseset
1368 1363 ui.log(
1369 1364 b"rebase",
1370 1365 b"rebasing working copy parent: %r\n",
1371 1366 rebasingwcp,
1372 1367 rebase_rebasing_wcp=rebasingwcp,
1373 1368 )
1374 1369 if inmemory and rebasingwcp:
1375 1370 # Check these since we did not before.
1376 1371 cmdutil.checkunfinished(repo)
1377 1372 cmdutil.bailifchanged(repo)
1378 1373
1379 1374 if not destf:
1380 1375 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
1381 1376 destf = bytes(dest)
1382 1377
1383 1378 allsrc = revsetlang.formatspec(b'%ld', rebaseset)
1384 1379 alias = {b'ALLSRC': allsrc}
1385 1380
1386 1381 if dest is None:
1387 1382 try:
1388 1383 # fast path: try to resolve dest without SRC alias
1389 1384 dest = scmutil.revsingle(repo, destf, localalias=alias)
1390 1385 except error.RepoLookupError:
1391 1386 # multi-dest path: resolve dest for each SRC separately
1392 1387 destmap = {}
1393 1388 for r in rebaseset:
1394 1389 alias[b'SRC'] = revsetlang.formatspec(b'%d', r)
1395 1390 # use repo.anyrevs instead of scmutil.revsingle because we
1396 1391 # don't want to abort if destset is empty.
1397 1392 destset = repo.anyrevs([destf], user=True, localalias=alias)
1398 1393 size = len(destset)
1399 1394 if size == 1:
1400 1395 destmap[r] = destset.first()
1401 1396 elif size == 0:
1402 1397 ui.note(_(b'skipping %s - empty destination\n') % repo[r])
1403 1398 else:
1404 1399 raise error.Abort(
1405 1400 _(b'rebase destination for %s is not unique') % repo[r]
1406 1401 )
1407 1402
1408 1403 if dest is not None:
1409 1404 # single-dest case: assign dest to each rev in rebaseset
1410 1405 destrev = dest.rev()
1411 1406 destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
1412 1407
1413 1408 if not destmap:
1414 1409 ui.status(_(b'nothing to rebase - empty destination\n'))
1415 1410 return None
1416 1411
1417 1412 return destmap
1418 1413
1419 1414
1420 1415 def externalparent(repo, state, destancestors):
1421 1416 """Return the revision that should be used as the second parent
1422 1417 when the revisions in state is collapsed on top of destancestors.
1423 1418 Abort if there is more than one parent.
1424 1419 """
1425 1420 parents = set()
1426 1421 source = min(state)
1427 1422 for rev in state:
1428 1423 if rev == source:
1429 1424 continue
1430 1425 for p in repo[rev].parents():
1431 1426 if p.rev() not in state and p.rev() not in destancestors:
1432 1427 parents.add(p.rev())
1433 1428 if not parents:
1434 1429 return nullrev
1435 1430 if len(parents) == 1:
1436 1431 return parents.pop()
1437 1432 raise error.Abort(
1438 1433 _(
1439 1434 b'unable to collapse on top of %d, there is more '
1440 1435 b'than one external parent: %s'
1441 1436 )
1442 1437 % (max(destancestors), b', '.join(b"%d" % p for p in sorted(parents)))
1443 1438 )
1444 1439
1445 1440
1446 1441 def commitmemorynode(repo, p1, p2, wctx, editor, extra, user, date, commitmsg):
1447 1442 '''Commit the memory changes with parents p1 and p2.
1448 1443 Return node of committed revision.'''
1449 1444 # Replicates the empty check in ``repo.commit``.
1450 1445 if wctx.isempty() and not repo.ui.configbool(b'ui', b'allowemptycommit'):
1451 1446 return None
1452 1447
1453 1448 # By convention, ``extra['branch']`` (set by extrafn) clobbers
1454 1449 # ``branch`` (used when passing ``--keepbranches``).
1455 1450 branch = repo[p1].branch()
1456 1451 if b'branch' in extra:
1457 1452 branch = extra[b'branch']
1458 1453
1459 1454 memctx = wctx.tomemctx(
1460 1455 commitmsg,
1461 1456 parents=(p1, p2),
1462 1457 date=date,
1463 1458 extra=extra,
1464 1459 user=user,
1465 1460 branch=branch,
1466 1461 editor=editor,
1467 1462 )
1468 1463 commitres = repo.commitctx(memctx)
1469 1464 wctx.clean() # Might be reused
1470 1465 return commitres
1471 1466
1472 1467
1473 1468 def commitnode(repo, p1, p2, editor, extra, user, date, commitmsg):
1474 1469 '''Commit the wd changes with parents p1 and p2.
1475 1470 Return node of committed revision.'''
1476 1471 dsguard = util.nullcontextmanager()
1477 1472 if not repo.ui.configbool(b'rebase', b'singletransaction'):
1478 1473 dsguard = dirstateguard.dirstateguard(repo, b'rebase')
1479 1474 with dsguard:
1480 1475 repo.setparents(repo[p1].node(), repo[p2].node())
1481 1476
1482 1477 # Commit might fail if unresolved files exist
1483 1478 newnode = repo.commit(
1484 1479 text=commitmsg, user=user, date=date, extra=extra, editor=editor
1485 1480 )
1486 1481
1487 1482 repo.dirstate.setbranch(repo[newnode].branch())
1488 1483 return newnode
1489 1484
1490 1485
1491 1486 def rebasenode(repo, rev, p1, base, collapse, dest, wctx):
1492 1487 """Rebase a single revision rev on top of p1 using base as merge ancestor"""
1493 1488 # Merge phase
1494 1489 # Update to destination and merge it with local
1495 1490 if wctx.isinmemory():
1496 1491 wctx.setbase(repo[p1])
1497 1492 else:
1498 1493 if repo[b'.'].rev() != p1:
1499 1494 repo.ui.debug(b" update to %d:%s\n" % (p1, repo[p1]))
1500 1495 mergemod.update(repo, p1, branchmerge=False, force=True)
1501 1496 else:
1502 1497 repo.ui.debug(b" already in destination\n")
1503 1498 # This is, alas, necessary to invalidate workingctx's manifest cache,
1504 1499 # as well as other data we litter on it in other places.
1505 1500 wctx = repo[None]
1506 1501 repo.dirstate.write(repo.currenttransaction())
1507 1502 repo.ui.debug(b" merge against %d:%s\n" % (rev, repo[rev]))
1508 1503 if base is not None:
1509 1504 repo.ui.debug(b" detach base %d:%s\n" % (base, repo[base]))
1510 1505 # When collapsing in-place, the parent is the common ancestor, we
1511 1506 # have to allow merging with it.
1512 1507 stats = mergemod.update(
1513 1508 repo,
1514 1509 rev,
1515 1510 branchmerge=True,
1516 1511 force=True,
1517 1512 ancestor=base,
1518 1513 mergeancestor=collapse,
1519 1514 labels=[b'dest', b'source'],
1520 1515 wc=wctx,
1521 1516 )
1522 1517 if collapse:
1523 1518 copies.duplicatecopies(repo, wctx, rev, dest)
1524 1519 else:
1525 1520 # If we're not using --collapse, we need to
1526 1521 # duplicate copies between the revision we're
1527 1522 # rebasing and its first parent, but *not*
1528 1523 # duplicate any copies that have already been
1529 1524 # performed in the destination.
1530 1525 p1rev = repo[rev].p1().rev()
1531 1526 copies.duplicatecopies(repo, wctx, rev, p1rev, skiprev=dest)
1532 1527 return stats
1533 1528
1534 1529
1535 1530 def adjustdest(repo, rev, destmap, state, skipped):
1536 1531 r"""adjust rebase destination given the current rebase state
1537 1532
1538 1533 rev is what is being rebased. Return a list of two revs, which are the
1539 1534 adjusted destinations for rev's p1 and p2, respectively. If a parent is
1540 1535 nullrev, return dest without adjustment for it.
1541 1536
1542 1537 For example, when doing rebasing B+E to F, C to G, rebase will first move B
1543 1538 to B1, and E's destination will be adjusted from F to B1.
1544 1539
1545 1540 B1 <- written during rebasing B
1546 1541 |
1547 1542 F <- original destination of B, E
1548 1543 |
1549 1544 | E <- rev, which is being rebased
1550 1545 | |
1551 1546 | D <- prev, one parent of rev being checked
1552 1547 | |
1553 1548 | x <- skipped, ex. no successor or successor in (::dest)
1554 1549 | |
1555 1550 | C <- rebased as C', different destination
1556 1551 | |
1557 1552 | B <- rebased as B1 C'
1558 1553 |/ |
1559 1554 A G <- destination of C, different
1560 1555
1561 1556 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
1562 1557 first move C to C1, G to G1, and when it's checking H, the adjusted
1563 1558 destinations will be [C1, G1].
1564 1559
1565 1560 H C1 G1
1566 1561 /| | /
1567 1562 F G |/
1568 1563 K | | -> K
1569 1564 | C D |
1570 1565 | |/ |
1571 1566 | B | ...
1572 1567 |/ |/
1573 1568 A A
1574 1569
1575 1570 Besides, adjust dest according to existing rebase information. For example,
1576 1571
1577 1572 B C D B needs to be rebased on top of C, C needs to be rebased on top
1578 1573 \|/ of D. We will rebase C first.
1579 1574 A
1580 1575
1581 1576 C' After rebasing C, when considering B's destination, use C'
1582 1577 | instead of the original C.
1583 1578 B D
1584 1579 \ /
1585 1580 A
1586 1581 """
1587 1582 # pick already rebased revs with same dest from state as interesting source
1588 1583 dest = destmap[rev]
1589 1584 source = [
1590 1585 s
1591 1586 for s, d in state.items()
1592 1587 if d > 0 and destmap[s] == dest and s not in skipped
1593 1588 ]
1594 1589
1595 1590 result = []
1596 1591 for prev in repo.changelog.parentrevs(rev):
1597 1592 adjusted = dest
1598 1593 if prev != nullrev:
1599 1594 candidate = repo.revs(b'max(%ld and (::%d))', source, prev).first()
1600 1595 if candidate is not None:
1601 1596 adjusted = state[candidate]
1602 1597 if adjusted == dest and dest in state:
1603 1598 adjusted = state[dest]
1604 1599 if adjusted == revtodo:
1605 1600 # sortsource should produce an order that makes this impossible
1606 1601 raise error.ProgrammingError(
1607 1602 b'rev %d should be rebased already at this time' % dest
1608 1603 )
1609 1604 result.append(adjusted)
1610 1605 return result
1611 1606
1612 1607
1613 1608 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
1614 1609 """
1615 1610 Abort if rebase will create divergence or rebase is noop because of markers
1616 1611
1617 1612 `rebaseobsrevs`: set of obsolete revision in source
1618 1613 `rebaseobsskipped`: set of revisions from source skipped because they have
1619 1614 successors in destination or no non-obsolete successor.
1620 1615 """
1621 1616 # Obsolete node with successors not in dest leads to divergence
1622 1617 divergenceok = ui.configbool(b'experimental', b'evolution.allowdivergence')
1623 1618 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
1624 1619
1625 1620 if divergencebasecandidates and not divergenceok:
1626 1621 divhashes = (bytes(repo[r]) for r in divergencebasecandidates)
1627 1622 msg = _(b"this rebase will cause divergences from: %s")
1628 1623 h = _(
1629 1624 b"to force the rebase please set "
1630 1625 b"experimental.evolution.allowdivergence=True"
1631 1626 )
1632 1627 raise error.Abort(msg % (b",".join(divhashes),), hint=h)
1633 1628
1634 1629
1635 1630 def successorrevs(unfi, rev):
1636 1631 """yield revision numbers for successors of rev"""
1637 1632 assert unfi.filtername is None
1638 1633 get_rev = unfi.changelog.index.get_rev
1639 1634 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
1640 1635 r = get_rev(s)
1641 1636 if r is not None:
1642 1637 yield r
1643 1638
1644 1639
1645 1640 def defineparents(repo, rev, destmap, state, skipped, obsskipped):
1646 1641 """Return new parents and optionally a merge base for rev being rebased
1647 1642
1648 1643 The destination specified by "dest" cannot always be used directly because
1649 1644 previously rebase result could affect destination. For example,
1650 1645
1651 1646 D E rebase -r C+D+E -d B
1652 1647 |/ C will be rebased to C'
1653 1648 B C D's new destination will be C' instead of B
1654 1649 |/ E's new destination will be C' instead of B
1655 1650 A
1656 1651
1657 1652 The new parents of a merge is slightly more complicated. See the comment
1658 1653 block below.
1659 1654 """
1660 1655 # use unfiltered changelog since successorrevs may return filtered nodes
1661 1656 assert repo.filtername is None
1662 1657 cl = repo.changelog
1663 1658 isancestor = cl.isancestorrev
1664 1659
1665 1660 dest = destmap[rev]
1666 1661 oldps = repo.changelog.parentrevs(rev) # old parents
1667 1662 newps = [nullrev, nullrev] # new parents
1668 1663 dests = adjustdest(repo, rev, destmap, state, skipped)
1669 1664 bases = list(oldps) # merge base candidates, initially just old parents
1670 1665
1671 1666 if all(r == nullrev for r in oldps[1:]):
1672 1667 # For non-merge changeset, just move p to adjusted dest as requested.
1673 1668 newps[0] = dests[0]
1674 1669 else:
1675 1670 # For merge changeset, if we move p to dests[i] unconditionally, both
1676 1671 # parents may change and the end result looks like "the merge loses a
1677 1672 # parent", which is a surprise. This is a limit because "--dest" only
1678 1673 # accepts one dest per src.
1679 1674 #
1680 1675 # Therefore, only move p with reasonable conditions (in this order):
1681 1676 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1682 1677 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1683 1678 #
1684 1679 # Comparing with adjustdest, the logic here does some additional work:
1685 1680 # 1. decide which parents will not be moved towards dest
1686 1681 # 2. if the above decision is "no", should a parent still be moved
1687 1682 # because it was rebased?
1688 1683 #
1689 1684 # For example:
1690 1685 #
1691 1686 # C # "rebase -r C -d D" is an error since none of the parents
1692 1687 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1693 1688 # A B D # B (using rule "2."), since B will be rebased.
1694 1689 #
1695 1690 # The loop tries to be not rely on the fact that a Mercurial node has
1696 1691 # at most 2 parents.
1697 1692 for i, p in enumerate(oldps):
1698 1693 np = p # new parent
1699 1694 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1700 1695 np = dests[i]
1701 1696 elif p in state and state[p] > 0:
1702 1697 np = state[p]
1703 1698
1704 1699 # "bases" only record "special" merge bases that cannot be
1705 1700 # calculated from changelog DAG (i.e. isancestor(p, np) is False).
1706 1701 # For example:
1707 1702 #
1708 1703 # B' # rebase -s B -d D, when B was rebased to B'. dest for C
1709 1704 # | C # is B', but merge base for C is B, instead of
1710 1705 # D | # changelog.ancestor(C, B') == A. If changelog DAG and
1711 1706 # | B # "state" edges are merged (so there will be an edge from
1712 1707 # |/ # B to B'), the merge base is still ancestor(C, B') in
1713 1708 # A # the merged graph.
1714 1709 #
1715 1710 # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
1716 1711 # which uses "virtual null merge" to explain this situation.
1717 1712 if isancestor(p, np):
1718 1713 bases[i] = nullrev
1719 1714
1720 1715 # If one parent becomes an ancestor of the other, drop the ancestor
1721 1716 for j, x in enumerate(newps[:i]):
1722 1717 if x == nullrev:
1723 1718 continue
1724 1719 if isancestor(np, x): # CASE-1
1725 1720 np = nullrev
1726 1721 elif isancestor(x, np): # CASE-2
1727 1722 newps[j] = np
1728 1723 np = nullrev
1729 1724 # New parents forming an ancestor relationship does not
1730 1725 # mean the old parents have a similar relationship. Do not
1731 1726 # set bases[x] to nullrev.
1732 1727 bases[j], bases[i] = bases[i], bases[j]
1733 1728
1734 1729 newps[i] = np
1735 1730
1736 1731 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1737 1732 # base. If only p2 changes, merging using unchanged p1 as merge base is
1738 1733 # suboptimal. Therefore swap parents to make the merge sane.
1739 1734 if newps[1] != nullrev and oldps[0] == newps[0]:
1740 1735 assert len(newps) == 2 and len(oldps) == 2
1741 1736 newps.reverse()
1742 1737 bases.reverse()
1743 1738
1744 1739 # No parent change might be an error because we fail to make rev a
1745 1740 # descendent of requested dest. This can happen, for example:
1746 1741 #
1747 1742 # C # rebase -r C -d D
1748 1743 # /| # None of A and B will be changed to D and rebase fails.
1749 1744 # A B D
1750 1745 if set(newps) == set(oldps) and dest not in newps:
1751 1746 raise error.Abort(
1752 1747 _(
1753 1748 b'cannot rebase %d:%s without '
1754 1749 b'moving at least one of its parents'
1755 1750 )
1756 1751 % (rev, repo[rev])
1757 1752 )
1758 1753
1759 1754 # Source should not be ancestor of dest. The check here guarantees it's
1760 1755 # impossible. With multi-dest, the initial check does not cover complex
1761 1756 # cases since we don't have abstractions to dry-run rebase cheaply.
1762 1757 if any(p != nullrev and isancestor(rev, p) for p in newps):
1763 1758 raise error.Abort(_(b'source is ancestor of destination'))
1764 1759
1765 1760 # "rebasenode" updates to new p1, use the corresponding merge base.
1766 1761 if bases[0] != nullrev:
1767 1762 base = bases[0]
1768 1763 else:
1769 1764 base = None
1770 1765
1771 1766 # Check if the merge will contain unwanted changes. That may happen if
1772 1767 # there are multiple special (non-changelog ancestor) merge bases, which
1773 1768 # cannot be handled well by the 3-way merge algorithm. For example:
1774 1769 #
1775 1770 # F
1776 1771 # /|
1777 1772 # D E # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
1778 1773 # | | # as merge base, the difference between D and F will include
1779 1774 # B C # C, so the rebased F will contain C surprisingly. If "E" was
1780 1775 # |/ # chosen, the rebased F will contain B.
1781 1776 # A Z
1782 1777 #
1783 1778 # But our merge base candidates (D and E in above case) could still be
1784 1779 # better than the default (ancestor(F, Z) == null). Therefore still
1785 1780 # pick one (so choose p1 above).
1786 1781 if sum(1 for b in set(bases) if b != nullrev) > 1:
1787 1782 unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
1788 1783 for i, base in enumerate(bases):
1789 1784 if base == nullrev:
1790 1785 continue
1791 1786 # Revisions in the side (not chosen as merge base) branch that
1792 1787 # might contain "surprising" contents
1793 1788 siderevs = list(
1794 1789 repo.revs(b'((%ld-%d) %% (%d+%d))', bases, base, base, dest)
1795 1790 )
1796 1791
1797 1792 # If those revisions are covered by rebaseset, the result is good.
1798 1793 # A merge in rebaseset would be considered to cover its ancestors.
1799 1794 if siderevs:
1800 1795 rebaseset = [
1801 1796 r for r, d in state.items() if d > 0 and r not in obsskipped
1802 1797 ]
1803 1798 merges = [
1804 1799 r for r in rebaseset if cl.parentrevs(r)[1] != nullrev
1805 1800 ]
1806 1801 unwanted[i] = list(
1807 1802 repo.revs(
1808 1803 b'%ld - (::%ld) - %ld', siderevs, merges, rebaseset
1809 1804 )
1810 1805 )
1811 1806
1812 1807 # Choose a merge base that has a minimal number of unwanted revs.
1813 1808 l, i = min(
1814 1809 (len(revs), i)
1815 1810 for i, revs in enumerate(unwanted)
1816 1811 if revs is not None
1817 1812 )
1818 1813 base = bases[i]
1819 1814
1820 1815 # newps[0] should match merge base if possible. Currently, if newps[i]
1821 1816 # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
1822 1817 # the other's ancestor. In that case, it's fine to not swap newps here.
1823 1818 # (see CASE-1 and CASE-2 above)
1824 1819 if i != 0 and newps[i] != nullrev:
1825 1820 newps[0], newps[i] = newps[i], newps[0]
1826 1821
1827 1822 # The merge will include unwanted revisions. Abort now. Revisit this if
1828 1823 # we have a more advanced merge algorithm that handles multiple bases.
1829 1824 if l > 0:
1830 1825 unwanteddesc = _(b' or ').join(
1831 1826 (
1832 1827 b', '.join(b'%d:%s' % (r, repo[r]) for r in revs)
1833 1828 for revs in unwanted
1834 1829 if revs is not None
1835 1830 )
1836 1831 )
1837 1832 raise error.Abort(
1838 1833 _(b'rebasing %d:%s will include unwanted changes from %s')
1839 1834 % (rev, repo[rev], unwanteddesc)
1840 1835 )
1841 1836
1842 1837 repo.ui.debug(b" future parents are %d and %d\n" % tuple(newps))
1843 1838
1844 1839 return newps[0], newps[1], base
1845 1840
1846 1841
1847 1842 def isagitpatch(repo, patchname):
1848 1843 """Return true if the given patch is in git format"""
1849 1844 mqpatch = os.path.join(repo.mq.path, patchname)
1850 1845 for line in patch.linereader(open(mqpatch, b'rb')):
1851 1846 if line.startswith(b'diff --git'):
1852 1847 return True
1853 1848 return False
1854 1849
1855 1850
1856 1851 def updatemq(repo, state, skipped, **opts):
1857 1852 """Update rebased mq patches - finalize and then import them"""
1858 1853 mqrebase = {}
1859 1854 mq = repo.mq
1860 1855 original_series = mq.fullseries[:]
1861 1856 skippedpatches = set()
1862 1857
1863 1858 for p in mq.applied:
1864 1859 rev = repo[p.node].rev()
1865 1860 if rev in state:
1866 1861 repo.ui.debug(
1867 1862 b'revision %d is an mq patch (%s), finalize it.\n'
1868 1863 % (rev, p.name)
1869 1864 )
1870 1865 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1871 1866 else:
1872 1867 # Applied but not rebased, not sure this should happen
1873 1868 skippedpatches.add(p.name)
1874 1869
1875 1870 if mqrebase:
1876 1871 mq.finish(repo, mqrebase.keys())
1877 1872
1878 1873 # We must start import from the newest revision
1879 1874 for rev in sorted(mqrebase, reverse=True):
1880 1875 if rev not in skipped:
1881 1876 name, isgit = mqrebase[rev]
1882 1877 repo.ui.note(
1883 1878 _(b'updating mq patch %s to %d:%s\n')
1884 1879 % (name, state[rev], repo[state[rev]])
1885 1880 )
1886 1881 mq.qimport(
1887 1882 repo,
1888 1883 (),
1889 1884 patchname=name,
1890 1885 git=isgit,
1891 1886 rev=[b"%d" % state[rev]],
1892 1887 )
1893 1888 else:
1894 1889 # Rebased and skipped
1895 1890 skippedpatches.add(mqrebase[rev][0])
1896 1891
1897 1892 # Patches were either applied and rebased and imported in
1898 1893 # order, applied and removed or unapplied. Discard the removed
1899 1894 # ones while preserving the original series order and guards.
1900 1895 newseries = [
1901 1896 s
1902 1897 for s in original_series
1903 1898 if mq.guard_re.split(s, 1)[0] not in skippedpatches
1904 1899 ]
1905 1900 mq.fullseries[:] = newseries
1906 1901 mq.seriesdirty = True
1907 1902 mq.savedirty()
1908 1903
1909 1904
1910 1905 def storecollapsemsg(repo, collapsemsg):
1911 1906 """Store the collapse message to allow recovery"""
1912 1907 collapsemsg = collapsemsg or b''
1913 1908 f = repo.vfs(b"last-message.txt", b"w")
1914 1909 f.write(b"%s\n" % collapsemsg)
1915 1910 f.close()
1916 1911
1917 1912
1918 1913 def clearcollapsemsg(repo):
1919 1914 """Remove collapse message file"""
1920 1915 repo.vfs.unlinkpath(b"last-message.txt", ignoremissing=True)
1921 1916
1922 1917
1923 1918 def restorecollapsemsg(repo, isabort):
1924 1919 """Restore previously stored collapse message"""
1925 1920 try:
1926 1921 f = repo.vfs(b"last-message.txt")
1927 1922 collapsemsg = f.readline().strip()
1928 1923 f.close()
1929 1924 except IOError as err:
1930 1925 if err.errno != errno.ENOENT:
1931 1926 raise
1932 1927 if isabort:
1933 1928 # Oh well, just abort like normal
1934 1929 collapsemsg = b''
1935 1930 else:
1936 1931 raise error.Abort(_(b'missing .hg/last-message.txt for rebase'))
1937 1932 return collapsemsg
1938 1933
1939 1934
1940 1935 def clearstatus(repo):
1941 1936 """Remove the status files"""
1942 1937 # Make sure the active transaction won't write the state file
1943 1938 tr = repo.currenttransaction()
1944 1939 if tr:
1945 1940 tr.removefilegenerator(b'rebasestate')
1946 1941 repo.vfs.unlinkpath(b"rebasestate", ignoremissing=True)
1947 1942
1948 1943
1949 1944 def needupdate(repo, state):
1950 1945 '''check whether we should `update --clean` away from a merge, or if
1951 1946 somehow the working dir got forcibly updated, e.g. by older hg'''
1952 1947 parents = [p.rev() for p in repo[None].parents()]
1953 1948
1954 1949 # Are we in a merge state at all?
1955 1950 if len(parents) < 2:
1956 1951 return False
1957 1952
1958 1953 # We should be standing on the first as-of-yet unrebased commit.
1959 1954 firstunrebased = min(
1960 1955 [old for old, new in pycompat.iteritems(state) if new == nullrev]
1961 1956 )
1962 1957 if firstunrebased in parents:
1963 1958 return True
1964 1959
1965 1960 return False
1966 1961
1967 1962
1968 1963 def sortsource(destmap):
1969 1964 """yield source revisions in an order that we only rebase things once
1970 1965
1971 1966 If source and destination overlaps, we should filter out revisions
1972 1967 depending on other revisions which hasn't been rebased yet.
1973 1968
1974 1969 Yield a sorted list of revisions each time.
1975 1970
1976 1971 For example, when rebasing A to B, B to C. This function yields [B], then
1977 1972 [A], indicating B needs to be rebased first.
1978 1973
1979 1974 Raise if there is a cycle so the rebase is impossible.
1980 1975 """
1981 1976 srcset = set(destmap)
1982 1977 while srcset:
1983 1978 srclist = sorted(srcset)
1984 1979 result = []
1985 1980 for r in srclist:
1986 1981 if destmap[r] not in srcset:
1987 1982 result.append(r)
1988 1983 if not result:
1989 1984 raise error.Abort(_(b'source and destination form a cycle'))
1990 1985 srcset -= set(result)
1991 1986 yield result
1992 1987
1993 1988
1994 1989 def buildstate(repo, destmap, collapse):
1995 1990 '''Define which revisions are going to be rebased and where
1996 1991
1997 1992 repo: repo
1998 1993 destmap: {srcrev: destrev}
1999 1994 '''
2000 1995 rebaseset = destmap.keys()
2001 1996 originalwd = repo[b'.'].rev()
2002 1997
2003 1998 # This check isn't strictly necessary, since mq detects commits over an
2004 1999 # applied patch. But it prevents messing up the working directory when
2005 2000 # a partially completed rebase is blocked by mq.
2006 2001 if b'qtip' in repo.tags():
2007 2002 mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
2008 2003 if set(destmap.values()) & mqapplied:
2009 2004 raise error.Abort(_(b'cannot rebase onto an applied mq patch'))
2010 2005
2011 2006 # Get "cycle" error early by exhausting the generator.
2012 2007 sortedsrc = list(sortsource(destmap)) # a list of sorted revs
2013 2008 if not sortedsrc:
2014 2009 raise error.Abort(_(b'no matching revisions'))
2015 2010
2016 2011 # Only check the first batch of revisions to rebase not depending on other
2017 2012 # rebaseset. This means "source is ancestor of destination" for the second
2018 2013 # (and following) batches of revisions are not checked here. We rely on
2019 2014 # "defineparents" to do that check.
2020 2015 roots = list(repo.set(b'roots(%ld)', sortedsrc[0]))
2021 2016 if not roots:
2022 2017 raise error.Abort(_(b'no matching revisions'))
2023 2018
2024 2019 def revof(r):
2025 2020 return r.rev()
2026 2021
2027 2022 roots = sorted(roots, key=revof)
2028 2023 state = dict.fromkeys(rebaseset, revtodo)
2029 2024 emptyrebase = len(sortedsrc) == 1
2030 2025 for root in roots:
2031 2026 dest = repo[destmap[root.rev()]]
2032 2027 commonbase = root.ancestor(dest)
2033 2028 if commonbase == root:
2034 2029 raise error.Abort(_(b'source is ancestor of destination'))
2035 2030 if commonbase == dest:
2036 2031 wctx = repo[None]
2037 2032 if dest == wctx.p1():
2038 2033 # when rebasing to '.', it will use the current wd branch name
2039 2034 samebranch = root.branch() == wctx.branch()
2040 2035 else:
2041 2036 samebranch = root.branch() == dest.branch()
2042 2037 if not collapse and samebranch and dest in root.parents():
2043 2038 # mark the revision as done by setting its new revision
2044 2039 # equal to its old (current) revisions
2045 2040 state[root.rev()] = root.rev()
2046 2041 repo.ui.debug(b'source is a child of destination\n')
2047 2042 continue
2048 2043
2049 2044 emptyrebase = False
2050 2045 repo.ui.debug(b'rebase onto %s starting from %s\n' % (dest, root))
2051 2046 if emptyrebase:
2052 2047 return None
2053 2048 for rev in sorted(state):
2054 2049 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
2055 2050 # if all parents of this revision are done, then so is this revision
2056 2051 if parents and all((state.get(p) == p for p in parents)):
2057 2052 state[rev] = rev
2058 2053 return originalwd, destmap, state
2059 2054
2060 2055
2061 2056 def clearrebased(
2062 2057 ui,
2063 2058 repo,
2064 2059 destmap,
2065 2060 state,
2066 2061 skipped,
2067 2062 collapsedas=None,
2068 2063 keepf=False,
2069 2064 fm=None,
2070 2065 backup=True,
2071 2066 ):
2072 2067 """dispose of rebased revision at the end of the rebase
2073 2068
2074 2069 If `collapsedas` is not None, the rebase was a collapse whose result if the
2075 2070 `collapsedas` node.
2076 2071
2077 2072 If `keepf` is not True, the rebase has --keep set and no nodes should be
2078 2073 removed (but bookmarks still need to be moved).
2079 2074
2080 2075 If `backup` is False, no backup will be stored when stripping rebased
2081 2076 revisions.
2082 2077 """
2083 2078 tonode = repo.changelog.node
2084 2079 replacements = {}
2085 2080 moves = {}
2086 2081 stripcleanup = not obsolete.isenabled(repo, obsolete.createmarkersopt)
2087 2082
2088 2083 collapsednodes = []
2089 2084 for rev, newrev in sorted(state.items()):
2090 2085 if newrev >= 0 and newrev != rev:
2091 2086 oldnode = tonode(rev)
2092 2087 newnode = collapsedas or tonode(newrev)
2093 2088 moves[oldnode] = newnode
2094 2089 succs = None
2095 2090 if rev in skipped:
2096 2091 if stripcleanup or not repo[rev].obsolete():
2097 2092 succs = ()
2098 2093 elif collapsedas:
2099 2094 collapsednodes.append(oldnode)
2100 2095 else:
2101 2096 succs = (newnode,)
2102 2097 if succs is not None:
2103 2098 replacements[(oldnode,)] = succs
2104 2099 if collapsednodes:
2105 2100 replacements[tuple(collapsednodes)] = (collapsedas,)
2106 2101 if fm:
2107 2102 hf = fm.hexfunc
2108 2103 fl = fm.formatlist
2109 2104 fd = fm.formatdict
2110 2105 changes = {}
2111 2106 for oldns, newn in pycompat.iteritems(replacements):
2112 2107 for oldn in oldns:
2113 2108 changes[hf(oldn)] = fl([hf(n) for n in newn], name=b'node')
2114 2109 nodechanges = fd(changes, key=b"oldnode", value=b"newnodes")
2115 2110 fm.data(nodechanges=nodechanges)
2116 2111 if keepf:
2117 2112 replacements = {}
2118 2113 scmutil.cleanupnodes(repo, replacements, b'rebase', moves, backup=backup)
2119 2114
2120 2115
2121 2116 def pullrebase(orig, ui, repo, *args, **opts):
2122 2117 """Call rebase after pull if the latter has been invoked with --rebase"""
2123 2118 if opts.get('rebase'):
2124 2119 if ui.configbool(b'commands', b'rebase.requiredest'):
2125 2120 msg = _(b'rebase destination required by configuration')
2126 2121 hint = _(b'use hg pull followed by hg rebase -d DEST')
2127 2122 raise error.Abort(msg, hint=hint)
2128 2123
2129 2124 with repo.wlock(), repo.lock():
2130 2125 if opts.get('update'):
2131 2126 del opts['update']
2132 2127 ui.debug(
2133 2128 b'--update and --rebase are not compatible, ignoring '
2134 2129 b'the update flag\n'
2135 2130 )
2136 2131
2137 2132 cmdutil.checkunfinished(repo, skipmerge=True)
2138 2133 cmdutil.bailifchanged(
2139 2134 repo,
2140 2135 hint=_(
2141 2136 b'cannot pull with rebase: '
2142 2137 b'please commit or shelve your changes first'
2143 2138 ),
2144 2139 )
2145 2140
2146 2141 revsprepull = len(repo)
2147 2142 origpostincoming = commands.postincoming
2148 2143
2149 2144 def _dummy(*args, **kwargs):
2150 2145 pass
2151 2146
2152 2147 commands.postincoming = _dummy
2153 2148 try:
2154 2149 ret = orig(ui, repo, *args, **opts)
2155 2150 finally:
2156 2151 commands.postincoming = origpostincoming
2157 2152 revspostpull = len(repo)
2158 2153 if revspostpull > revsprepull:
2159 2154 # --rev option from pull conflict with rebase own --rev
2160 2155 # dropping it
2161 2156 if 'rev' in opts:
2162 2157 del opts['rev']
2163 2158 # positional argument from pull conflicts with rebase's own
2164 2159 # --source.
2165 2160 if 'source' in opts:
2166 2161 del opts['source']
2167 2162 # revsprepull is the len of the repo, not revnum of tip.
2168 2163 destspace = list(repo.changelog.revs(start=revsprepull))
2169 2164 opts['_destspace'] = destspace
2170 2165 try:
2171 2166 rebase(ui, repo, **opts)
2172 2167 except error.NoMergeDestAbort:
2173 2168 # we can maybe update instead
2174 2169 rev, _a, _b = destutil.destupdate(repo)
2175 2170 if rev == repo[b'.'].rev():
2176 2171 ui.status(_(b'nothing to rebase\n'))
2177 2172 else:
2178 2173 ui.status(_(b'nothing to rebase - updating instead\n'))
2179 2174 # not passing argument to get the bare update behavior
2180 2175 # with warning and trumpets
2181 2176 commands.update(ui, repo)
2182 2177 else:
2183 2178 if opts.get('tool'):
2184 2179 raise error.Abort(_(b'--tool can only be used with --rebase'))
2185 2180 ret = orig(ui, repo, *args, **opts)
2186 2181
2187 2182 return ret
2188 2183
2189 2184
2190 2185 def _filterobsoleterevs(repo, revs):
2191 2186 """returns a set of the obsolete revisions in revs"""
2192 2187 return set(r for r in revs if repo[r].obsolete())
2193 2188
2194 2189
2195 2190 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
2196 2191 """Return (obsoletenotrebased, obsoletewithoutsuccessorindestination).
2197 2192
2198 2193 `obsoletenotrebased` is a mapping mapping obsolete => successor for all
2199 2194 obsolete nodes to be rebased given in `rebaseobsrevs`.
2200 2195
2201 2196 `obsoletewithoutsuccessorindestination` is a set with obsolete revisions
2202 2197 without a successor in destination.
2203 2198
2204 2199 `obsoleteextinctsuccessors` is a set of obsolete revisions with only
2205 2200 obsolete successors.
2206 2201 """
2207 2202 obsoletenotrebased = {}
2208 2203 obsoletewithoutsuccessorindestination = set()
2209 2204 obsoleteextinctsuccessors = set()
2210 2205
2211 2206 assert repo.filtername is None
2212 2207 cl = repo.changelog
2213 2208 get_rev = cl.index.get_rev
2214 2209 extinctrevs = set(repo.revs(b'extinct()'))
2215 2210 for srcrev in rebaseobsrevs:
2216 2211 srcnode = cl.node(srcrev)
2217 2212 # XXX: more advanced APIs are required to handle split correctly
2218 2213 successors = set(obsutil.allsuccessors(repo.obsstore, [srcnode]))
2219 2214 # obsutil.allsuccessors includes node itself
2220 2215 successors.remove(srcnode)
2221 2216 succrevs = {get_rev(s) for s in successors}
2222 2217 succrevs.discard(None)
2223 2218 if succrevs.issubset(extinctrevs):
2224 2219 # all successors are extinct
2225 2220 obsoleteextinctsuccessors.add(srcrev)
2226 2221 if not successors:
2227 2222 # no successor
2228 2223 obsoletenotrebased[srcrev] = None
2229 2224 else:
2230 2225 dstrev = destmap[srcrev]
2231 2226 for succrev in succrevs:
2232 2227 if cl.isancestorrev(succrev, dstrev):
2233 2228 obsoletenotrebased[srcrev] = succrev
2234 2229 break
2235 2230 else:
2236 2231 # If 'srcrev' has a successor in rebase set but none in
2237 2232 # destination (which would be catched above), we shall skip it
2238 2233 # and its descendants to avoid divergence.
2239 2234 if srcrev in extinctrevs or any(s in destmap for s in succrevs):
2240 2235 obsoletewithoutsuccessorindestination.add(srcrev)
2241 2236
2242 2237 return (
2243 2238 obsoletenotrebased,
2244 2239 obsoletewithoutsuccessorindestination,
2245 2240 obsoleteextinctsuccessors,
2246 2241 )
2247 2242
2248 2243
2249 2244 def abortrebase(ui, repo):
2250 2245 with repo.wlock(), repo.lock():
2251 2246 rbsrt = rebaseruntime(repo, ui)
2252 2247 rbsrt._prepareabortorcontinue(isabort=True)
2253 2248
2254 2249
2255 2250 def continuerebase(ui, repo):
2256 2251 with repo.wlock(), repo.lock():
2257 2252 rbsrt = rebaseruntime(repo, ui)
2258 2253 ms = mergemod.mergestate.read(repo)
2259 2254 mergeutil.checkunresolved(ms)
2260 2255 retcode = rbsrt._prepareabortorcontinue(isabort=False)
2261 2256 if retcode is not None:
2262 2257 return retcode
2263 2258 rbsrt._performrebase(None)
2264 2259 rbsrt._finishrebase()
2265 2260
2266 2261
2267 2262 def summaryhook(ui, repo):
2268 2263 if not repo.vfs.exists(b'rebasestate'):
2269 2264 return
2270 2265 try:
2271 2266 rbsrt = rebaseruntime(repo, ui, {})
2272 2267 rbsrt.restorestatus()
2273 2268 state = rbsrt.state
2274 2269 except error.RepoLookupError:
2275 2270 # i18n: column positioning for "hg summary"
2276 2271 msg = _(b'rebase: (use "hg rebase --abort" to clear broken state)\n')
2277 2272 ui.write(msg)
2278 2273 return
2279 2274 numrebased = len([i for i in pycompat.itervalues(state) if i >= 0])
2280 2275 # i18n: column positioning for "hg summary"
2281 2276 ui.write(
2282 2277 _(b'rebase: %s, %s (rebase --continue)\n')
2283 2278 % (
2284 2279 ui.label(_(b'%d rebased'), b'rebase.rebased') % numrebased,
2285 2280 ui.label(_(b'%d remaining'), b'rebase.remaining')
2286 2281 % (len(state) - numrebased),
2287 2282 )
2288 2283 )
2289 2284
2290 2285
2291 2286 def uisetup(ui):
2292 2287 # Replace pull with a decorator to provide --rebase option
2293 2288 entry = extensions.wrapcommand(commands.table, b'pull', pullrebase)
2294 2289 entry[1].append(
2295 2290 (b'', b'rebase', None, _(b"rebase working directory to branch head"))
2296 2291 )
2297 2292 entry[1].append((b't', b'tool', b'', _(b"specify merge tool for rebase")))
2298 2293 cmdutil.summaryhooks.add(b'rebase', summaryhook)
2299 2294 statemod.addunfinished(
2300 2295 b'rebase',
2301 2296 fname=b'rebasestate',
2302 2297 stopflag=True,
2303 2298 continueflag=True,
2304 2299 abortfunc=abortrebase,
2305 2300 continuefunc=continuerebase,
2306 2301 )
@@ -1,521 +1,521
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 72 abort: abort and continue do not allow specifying revisions
73 73 [255]
74 74
75 75 $ hg rebase --base 5 --source 4
76 abort: cannot specify both a source and a base
76 abort: cannot specify both --source and --base
77 77 [255]
78 78
79 79 $ hg rebase --rev 5 --source 4
80 abort: cannot specify both a revision and a source
80 abort: cannot specify both --rev and --source
81 81 [255]
82 82 $ hg rebase --base 5 --rev 4
83 abort: cannot specify both a revision and a base
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