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