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