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