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