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