##// END OF EJS Templates
rebase: inline single-use variables passed to _definedestmap()...
Martin von Zweigbergk -
r44384:9fb9f3a5 default
parent child Browse files
Show More
@@ -1,2300 +1,2295 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 action = cmdutil.check_at_most_one_arg(opts, b'abort', b'stop', b'continue')
1023 1023 if action:
1024 1024 cmdutil.check_incompatible_arguments(
1025 1025 opts, action, b'confirm', b'dry_run'
1026 1026 )
1027 1027 cmdutil.check_incompatible_arguments(
1028 1028 opts, action, b'rev', b'source', b'base', b'dest'
1029 1029 )
1030 1030 cmdutil.check_at_most_one_arg(opts, b'confirm', b'dry_run')
1031 1031 cmdutil.check_at_most_one_arg(opts, b'rev', b'source', b'base')
1032 1032
1033 1033 if action or repo.currenttransaction() is not None:
1034 1034 # in-memory rebase is not compatible with resuming rebases.
1035 1035 # (Or if it is run within a transaction, since the restart logic can
1036 1036 # fail the entire transaction.)
1037 1037 inmemory = False
1038 1038
1039 1039 if opts.get(b'auto_orphans'):
1040 1040 disallowed_opts = set(opts) - {b'auto_orphans'}
1041 1041 cmdutil.check_incompatible_arguments(
1042 1042 opts, b'auto_orphans', *disallowed_opts
1043 1043 )
1044 1044
1045 1045 userrevs = list(repo.revs(opts.get(b'auto_orphans')))
1046 1046 opts[b'rev'] = [revsetlang.formatspec(b'%ld and orphan()', userrevs)]
1047 1047 opts[b'dest'] = b'_destautoorphanrebase(SRC)'
1048 1048
1049 1049 if opts.get(b'dry_run') or opts.get(b'confirm'):
1050 1050 return _dryrunrebase(ui, repo, action, opts)
1051 1051 elif action == b'stop':
1052 1052 rbsrt = rebaseruntime(repo, ui)
1053 1053 with repo.wlock(), repo.lock():
1054 1054 rbsrt.restorestatus()
1055 1055 if rbsrt.collapsef:
1056 1056 raise error.Abort(_(b"cannot stop in --collapse session"))
1057 1057 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1058 1058 if not (rbsrt.keepf or allowunstable):
1059 1059 raise error.Abort(
1060 1060 _(
1061 1061 b"cannot remove original changesets with"
1062 1062 b" unrebased descendants"
1063 1063 ),
1064 1064 hint=_(
1065 1065 b'either enable obsmarkers to allow unstable '
1066 1066 b'revisions or use --keep to keep original '
1067 1067 b'changesets'
1068 1068 ),
1069 1069 )
1070 1070 if needupdate(repo, rbsrt.state):
1071 1071 # update to the current working revision
1072 1072 # to clear interrupted merge
1073 1073 hg.updaterepo(repo, rbsrt.originalwd, overwrite=True)
1074 1074 rbsrt._finishrebase()
1075 1075 return 0
1076 1076 elif inmemory:
1077 1077 try:
1078 1078 # in-memory merge doesn't support conflicts, so if we hit any, abort
1079 1079 # and re-run as an on-disk merge.
1080 1080 overrides = {(b'rebase', b'singletransaction'): True}
1081 1081 with ui.configoverride(overrides, b'rebase'):
1082 1082 return _dorebase(ui, repo, action, opts, inmemory=inmemory)
1083 1083 except error.InMemoryMergeConflictsError:
1084 1084 ui.warn(
1085 1085 _(
1086 1086 b'hit merge conflicts; re-running rebase without in-memory'
1087 1087 b' merge\n'
1088 1088 )
1089 1089 )
1090 1090 # TODO: Make in-memory merge not use the on-disk merge state, so
1091 1091 # we don't have to clean it here
1092 1092 mergemod.mergestate.clean(repo)
1093 1093 clearstatus(repo)
1094 1094 clearcollapsemsg(repo)
1095 1095 return _dorebase(ui, repo, action, opts, inmemory=False)
1096 1096 else:
1097 1097 return _dorebase(ui, repo, action, opts)
1098 1098
1099 1099
1100 1100 def _dryrunrebase(ui, repo, action, opts):
1101 1101 rbsrt = rebaseruntime(repo, ui, inmemory=True, opts=opts)
1102 1102 confirm = opts.get(b'confirm')
1103 1103 if confirm:
1104 1104 ui.status(_(b'starting in-memory rebase\n'))
1105 1105 else:
1106 1106 ui.status(
1107 1107 _(b'starting dry-run rebase; repository will not be changed\n')
1108 1108 )
1109 1109 with repo.wlock(), repo.lock():
1110 1110 needsabort = True
1111 1111 try:
1112 1112 overrides = {(b'rebase', b'singletransaction'): True}
1113 1113 with ui.configoverride(overrides, b'rebase'):
1114 1114 _origrebase(
1115 1115 ui,
1116 1116 repo,
1117 1117 action,
1118 1118 opts,
1119 1119 rbsrt,
1120 1120 inmemory=True,
1121 1121 leaveunfinished=True,
1122 1122 )
1123 1123 except error.InMemoryMergeConflictsError:
1124 1124 ui.status(_(b'hit a merge conflict\n'))
1125 1125 return 1
1126 1126 except error.Abort:
1127 1127 needsabort = False
1128 1128 raise
1129 1129 else:
1130 1130 if confirm:
1131 1131 ui.status(_(b'rebase completed successfully\n'))
1132 1132 if not ui.promptchoice(_(b'apply changes (yn)?$$ &Yes $$ &No')):
1133 1133 # finish unfinished rebase
1134 1134 rbsrt._finishrebase()
1135 1135 else:
1136 1136 rbsrt._prepareabortorcontinue(
1137 1137 isabort=True, backup=False, suppwarns=True
1138 1138 )
1139 1139 needsabort = False
1140 1140 else:
1141 1141 ui.status(
1142 1142 _(
1143 1143 b'dry-run rebase completed successfully; run without'
1144 1144 b' -n/--dry-run to perform this rebase\n'
1145 1145 )
1146 1146 )
1147 1147 return 0
1148 1148 finally:
1149 1149 if needsabort:
1150 1150 # no need to store backup in case of dryrun
1151 1151 rbsrt._prepareabortorcontinue(
1152 1152 isabort=True, backup=False, suppwarns=True
1153 1153 )
1154 1154
1155 1155
1156 1156 def _dorebase(ui, repo, action, opts, inmemory=False):
1157 1157 rbsrt = rebaseruntime(repo, ui, inmemory, opts)
1158 1158 return _origrebase(ui, repo, action, opts, rbsrt, inmemory=inmemory)
1159 1159
1160 1160
1161 1161 def _origrebase(
1162 1162 ui, repo, action, opts, rbsrt, inmemory=False, leaveunfinished=False
1163 1163 ):
1164 1164 assert action != b'stop'
1165 1165 with repo.wlock(), repo.lock():
1166 # Validate input and define rebasing points
1167 destf = opts.get(b'dest', None)
1168 srcf = opts.get(b'source', None)
1169 basef = opts.get(b'base', None)
1170 revf = opts.get(b'rev', [])
1171 # search default destination in this space
1172 # used in the 'hg pull --rebase' case, see issue 5214.
1173 destspace = opts.get(b'_destspace')
1174 1166 if opts.get(b'interactive'):
1175 1167 try:
1176 1168 if extensions.find(b'histedit'):
1177 1169 enablehistedit = b''
1178 1170 except KeyError:
1179 1171 enablehistedit = b" --config extensions.histedit="
1180 1172 help = b"hg%s help -e histedit" % enablehistedit
1181 1173 msg = (
1182 1174 _(
1183 1175 b"interactive history editing is supported by the "
1184 1176 b"'histedit' extension (see \"%s\")"
1185 1177 )
1186 1178 % help
1187 1179 )
1188 1180 raise error.Abort(msg)
1189 1181
1190 1182 if rbsrt.collapsemsg and not rbsrt.collapsef:
1191 1183 raise error.Abort(_(b'message can only be specified with collapse'))
1192 1184
1193 1185 if action:
1194 1186 if rbsrt.collapsef:
1195 1187 raise error.Abort(
1196 1188 _(b'cannot use collapse with continue or abort')
1197 1189 )
1198 1190 if action == b'abort' and opts.get(b'tool', False):
1199 1191 ui.warn(_(b'tool option will be ignored\n'))
1200 1192 if action == b'continue':
1201 1193 ms = mergemod.mergestate.read(repo)
1202 1194 mergeutil.checkunresolved(ms)
1203 1195
1204 1196 retcode = rbsrt._prepareabortorcontinue(
1205 1197 isabort=(action == b'abort')
1206 1198 )
1207 1199 if retcode is not None:
1208 1200 return retcode
1209 1201 else:
1202 # search default destination in this space
1203 # used in the 'hg pull --rebase' case, see issue 5214.
1204 destspace = opts.get(b'_destspace')
1210 1205 destmap = _definedestmap(
1211 1206 ui,
1212 1207 repo,
1213 1208 inmemory,
1214 destf,
1215 srcf,
1216 basef,
1217 revf,
1209 opts.get(b'dest', None),
1210 opts.get(b'source', None),
1211 opts.get(b'base', None),
1212 opts.get(b'rev', []),
1218 1213 destspace=destspace,
1219 1214 )
1220 1215 retcode = rbsrt._preparenewrebase(destmap)
1221 1216 if retcode is not None:
1222 1217 return retcode
1223 1218 storecollapsemsg(repo, rbsrt.collapsemsg)
1224 1219
1225 1220 tr = None
1226 1221
1227 1222 singletr = ui.configbool(b'rebase', b'singletransaction')
1228 1223 if singletr:
1229 1224 tr = repo.transaction(b'rebase')
1230 1225
1231 1226 # If `rebase.singletransaction` is enabled, wrap the entire operation in
1232 1227 # one transaction here. Otherwise, transactions are obtained when
1233 1228 # committing each node, which is slower but allows partial success.
1234 1229 with util.acceptintervention(tr):
1235 1230 # Same logic for the dirstate guard, except we don't create one when
1236 1231 # rebasing in-memory (it's not needed).
1237 1232 dsguard = None
1238 1233 if singletr and not inmemory:
1239 1234 dsguard = dirstateguard.dirstateguard(repo, b'rebase')
1240 1235 with util.acceptintervention(dsguard):
1241 1236 rbsrt._performrebase(tr)
1242 1237 if not leaveunfinished:
1243 1238 rbsrt._finishrebase()
1244 1239
1245 1240
1246 1241 def _definedestmap(
1247 1242 ui,
1248 1243 repo,
1249 1244 inmemory,
1250 1245 destf=None,
1251 1246 srcf=None,
1252 1247 basef=None,
1253 1248 revf=None,
1254 1249 destspace=None,
1255 1250 ):
1256 1251 """use revisions argument to define destmap {srcrev: destrev}"""
1257 1252 if revf is None:
1258 1253 revf = []
1259 1254
1260 1255 # destspace is here to work around issues with `hg pull --rebase` see
1261 1256 # issue5214 for details
1262 1257
1263 1258 cmdutil.checkunfinished(repo)
1264 1259 if not inmemory:
1265 1260 cmdutil.bailifchanged(repo)
1266 1261
1267 1262 if ui.configbool(b'commands', b'rebase.requiredest') and not destf:
1268 1263 raise error.Abort(
1269 1264 _(b'you must specify a destination'),
1270 1265 hint=_(b'use: hg rebase -d REV'),
1271 1266 )
1272 1267
1273 1268 dest = None
1274 1269
1275 1270 if revf:
1276 1271 rebaseset = scmutil.revrange(repo, revf)
1277 1272 if not rebaseset:
1278 1273 ui.status(_(b'empty "rev" revision set - nothing to rebase\n'))
1279 1274 return None
1280 1275 elif srcf:
1281 1276 src = scmutil.revrange(repo, [srcf])
1282 1277 if not src:
1283 1278 ui.status(_(b'empty "source" revision set - nothing to rebase\n'))
1284 1279 return None
1285 1280 rebaseset = repo.revs(b'(%ld)::', src)
1286 1281 assert rebaseset
1287 1282 else:
1288 1283 base = scmutil.revrange(repo, [basef or b'.'])
1289 1284 if not base:
1290 1285 ui.status(
1291 1286 _(b'empty "base" revision set - ' b"can't compute rebase set\n")
1292 1287 )
1293 1288 return None
1294 1289 if destf:
1295 1290 # --base does not support multiple destinations
1296 1291 dest = scmutil.revsingle(repo, destf)
1297 1292 else:
1298 1293 dest = repo[_destrebase(repo, base, destspace=destspace)]
1299 1294 destf = bytes(dest)
1300 1295
1301 1296 roots = [] # selected children of branching points
1302 1297 bpbase = {} # {branchingpoint: [origbase]}
1303 1298 for b in base: # group bases by branching points
1304 1299 bp = repo.revs(b'ancestor(%d, %d)', b, dest.rev()).first()
1305 1300 bpbase[bp] = bpbase.get(bp, []) + [b]
1306 1301 if None in bpbase:
1307 1302 # emulate the old behavior, showing "nothing to rebase" (a better
1308 1303 # behavior may be abort with "cannot find branching point" error)
1309 1304 bpbase.clear()
1310 1305 for bp, bs in pycompat.iteritems(bpbase): # calculate roots
1311 1306 roots += list(repo.revs(b'children(%d) & ancestors(%ld)', bp, bs))
1312 1307
1313 1308 rebaseset = repo.revs(b'%ld::', roots)
1314 1309
1315 1310 if not rebaseset:
1316 1311 # transform to list because smartsets are not comparable to
1317 1312 # lists. This should be improved to honor laziness of
1318 1313 # smartset.
1319 1314 if list(base) == [dest.rev()]:
1320 1315 if basef:
1321 1316 ui.status(
1322 1317 _(
1323 1318 b'nothing to rebase - %s is both "base"'
1324 1319 b' and destination\n'
1325 1320 )
1326 1321 % dest
1327 1322 )
1328 1323 else:
1329 1324 ui.status(
1330 1325 _(
1331 1326 b'nothing to rebase - working directory '
1332 1327 b'parent is also destination\n'
1333 1328 )
1334 1329 )
1335 1330 elif not repo.revs(b'%ld - ::%d', base, dest.rev()):
1336 1331 if basef:
1337 1332 ui.status(
1338 1333 _(
1339 1334 b'nothing to rebase - "base" %s is '
1340 1335 b'already an ancestor of destination '
1341 1336 b'%s\n'
1342 1337 )
1343 1338 % (b'+'.join(bytes(repo[r]) for r in base), dest)
1344 1339 )
1345 1340 else:
1346 1341 ui.status(
1347 1342 _(
1348 1343 b'nothing to rebase - working '
1349 1344 b'directory parent is already an '
1350 1345 b'ancestor of destination %s\n'
1351 1346 )
1352 1347 % dest
1353 1348 )
1354 1349 else: # can it happen?
1355 1350 ui.status(
1356 1351 _(b'nothing to rebase from %s to %s\n')
1357 1352 % (b'+'.join(bytes(repo[r]) for r in base), dest)
1358 1353 )
1359 1354 return None
1360 1355
1361 1356 rebasingwcp = repo[b'.'].rev() in rebaseset
1362 1357 ui.log(
1363 1358 b"rebase",
1364 1359 b"rebasing working copy parent: %r\n",
1365 1360 rebasingwcp,
1366 1361 rebase_rebasing_wcp=rebasingwcp,
1367 1362 )
1368 1363 if inmemory and rebasingwcp:
1369 1364 # Check these since we did not before.
1370 1365 cmdutil.checkunfinished(repo)
1371 1366 cmdutil.bailifchanged(repo)
1372 1367
1373 1368 if not destf:
1374 1369 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
1375 1370 destf = bytes(dest)
1376 1371
1377 1372 allsrc = revsetlang.formatspec(b'%ld', rebaseset)
1378 1373 alias = {b'ALLSRC': allsrc}
1379 1374
1380 1375 if dest is None:
1381 1376 try:
1382 1377 # fast path: try to resolve dest without SRC alias
1383 1378 dest = scmutil.revsingle(repo, destf, localalias=alias)
1384 1379 except error.RepoLookupError:
1385 1380 # multi-dest path: resolve dest for each SRC separately
1386 1381 destmap = {}
1387 1382 for r in rebaseset:
1388 1383 alias[b'SRC'] = revsetlang.formatspec(b'%d', r)
1389 1384 # use repo.anyrevs instead of scmutil.revsingle because we
1390 1385 # don't want to abort if destset is empty.
1391 1386 destset = repo.anyrevs([destf], user=True, localalias=alias)
1392 1387 size = len(destset)
1393 1388 if size == 1:
1394 1389 destmap[r] = destset.first()
1395 1390 elif size == 0:
1396 1391 ui.note(_(b'skipping %s - empty destination\n') % repo[r])
1397 1392 else:
1398 1393 raise error.Abort(
1399 1394 _(b'rebase destination for %s is not unique') % repo[r]
1400 1395 )
1401 1396
1402 1397 if dest is not None:
1403 1398 # single-dest case: assign dest to each rev in rebaseset
1404 1399 destrev = dest.rev()
1405 1400 destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
1406 1401
1407 1402 if not destmap:
1408 1403 ui.status(_(b'nothing to rebase - empty destination\n'))
1409 1404 return None
1410 1405
1411 1406 return destmap
1412 1407
1413 1408
1414 1409 def externalparent(repo, state, destancestors):
1415 1410 """Return the revision that should be used as the second parent
1416 1411 when the revisions in state is collapsed on top of destancestors.
1417 1412 Abort if there is more than one parent.
1418 1413 """
1419 1414 parents = set()
1420 1415 source = min(state)
1421 1416 for rev in state:
1422 1417 if rev == source:
1423 1418 continue
1424 1419 for p in repo[rev].parents():
1425 1420 if p.rev() not in state and p.rev() not in destancestors:
1426 1421 parents.add(p.rev())
1427 1422 if not parents:
1428 1423 return nullrev
1429 1424 if len(parents) == 1:
1430 1425 return parents.pop()
1431 1426 raise error.Abort(
1432 1427 _(
1433 1428 b'unable to collapse on top of %d, there is more '
1434 1429 b'than one external parent: %s'
1435 1430 )
1436 1431 % (max(destancestors), b', '.join(b"%d" % p for p in sorted(parents)))
1437 1432 )
1438 1433
1439 1434
1440 1435 def commitmemorynode(repo, p1, p2, wctx, editor, extra, user, date, commitmsg):
1441 1436 '''Commit the memory changes with parents p1 and p2.
1442 1437 Return node of committed revision.'''
1443 1438 # Replicates the empty check in ``repo.commit``.
1444 1439 if wctx.isempty() and not repo.ui.configbool(b'ui', b'allowemptycommit'):
1445 1440 return None
1446 1441
1447 1442 # By convention, ``extra['branch']`` (set by extrafn) clobbers
1448 1443 # ``branch`` (used when passing ``--keepbranches``).
1449 1444 branch = repo[p1].branch()
1450 1445 if b'branch' in extra:
1451 1446 branch = extra[b'branch']
1452 1447
1453 1448 memctx = wctx.tomemctx(
1454 1449 commitmsg,
1455 1450 parents=(p1, p2),
1456 1451 date=date,
1457 1452 extra=extra,
1458 1453 user=user,
1459 1454 branch=branch,
1460 1455 editor=editor,
1461 1456 )
1462 1457 commitres = repo.commitctx(memctx)
1463 1458 wctx.clean() # Might be reused
1464 1459 return commitres
1465 1460
1466 1461
1467 1462 def commitnode(repo, p1, p2, editor, extra, user, date, commitmsg):
1468 1463 '''Commit the wd changes with parents p1 and p2.
1469 1464 Return node of committed revision.'''
1470 1465 dsguard = util.nullcontextmanager()
1471 1466 if not repo.ui.configbool(b'rebase', b'singletransaction'):
1472 1467 dsguard = dirstateguard.dirstateguard(repo, b'rebase')
1473 1468 with dsguard:
1474 1469 repo.setparents(repo[p1].node(), repo[p2].node())
1475 1470
1476 1471 # Commit might fail if unresolved files exist
1477 1472 newnode = repo.commit(
1478 1473 text=commitmsg, user=user, date=date, extra=extra, editor=editor
1479 1474 )
1480 1475
1481 1476 repo.dirstate.setbranch(repo[newnode].branch())
1482 1477 return newnode
1483 1478
1484 1479
1485 1480 def rebasenode(repo, rev, p1, base, collapse, dest, wctx):
1486 1481 """Rebase a single revision rev on top of p1 using base as merge ancestor"""
1487 1482 # Merge phase
1488 1483 # Update to destination and merge it with local
1489 1484 if wctx.isinmemory():
1490 1485 wctx.setbase(repo[p1])
1491 1486 else:
1492 1487 if repo[b'.'].rev() != p1:
1493 1488 repo.ui.debug(b" update to %d:%s\n" % (p1, repo[p1]))
1494 1489 mergemod.update(repo, p1, branchmerge=False, force=True)
1495 1490 else:
1496 1491 repo.ui.debug(b" already in destination\n")
1497 1492 # This is, alas, necessary to invalidate workingctx's manifest cache,
1498 1493 # as well as other data we litter on it in other places.
1499 1494 wctx = repo[None]
1500 1495 repo.dirstate.write(repo.currenttransaction())
1501 1496 repo.ui.debug(b" merge against %d:%s\n" % (rev, repo[rev]))
1502 1497 if base is not None:
1503 1498 repo.ui.debug(b" detach base %d:%s\n" % (base, repo[base]))
1504 1499 # When collapsing in-place, the parent is the common ancestor, we
1505 1500 # have to allow merging with it.
1506 1501 stats = mergemod.update(
1507 1502 repo,
1508 1503 rev,
1509 1504 branchmerge=True,
1510 1505 force=True,
1511 1506 ancestor=base,
1512 1507 mergeancestor=collapse,
1513 1508 labels=[b'dest', b'source'],
1514 1509 wc=wctx,
1515 1510 )
1516 1511 if collapse:
1517 1512 copies.duplicatecopies(repo, wctx, rev, dest)
1518 1513 else:
1519 1514 # If we're not using --collapse, we need to
1520 1515 # duplicate copies between the revision we're
1521 1516 # rebasing and its first parent, but *not*
1522 1517 # duplicate any copies that have already been
1523 1518 # performed in the destination.
1524 1519 p1rev = repo[rev].p1().rev()
1525 1520 copies.duplicatecopies(repo, wctx, rev, p1rev, skiprev=dest)
1526 1521 return stats
1527 1522
1528 1523
1529 1524 def adjustdest(repo, rev, destmap, state, skipped):
1530 1525 r"""adjust rebase destination given the current rebase state
1531 1526
1532 1527 rev is what is being rebased. Return a list of two revs, which are the
1533 1528 adjusted destinations for rev's p1 and p2, respectively. If a parent is
1534 1529 nullrev, return dest without adjustment for it.
1535 1530
1536 1531 For example, when doing rebasing B+E to F, C to G, rebase will first move B
1537 1532 to B1, and E's destination will be adjusted from F to B1.
1538 1533
1539 1534 B1 <- written during rebasing B
1540 1535 |
1541 1536 F <- original destination of B, E
1542 1537 |
1543 1538 | E <- rev, which is being rebased
1544 1539 | |
1545 1540 | D <- prev, one parent of rev being checked
1546 1541 | |
1547 1542 | x <- skipped, ex. no successor or successor in (::dest)
1548 1543 | |
1549 1544 | C <- rebased as C', different destination
1550 1545 | |
1551 1546 | B <- rebased as B1 C'
1552 1547 |/ |
1553 1548 A G <- destination of C, different
1554 1549
1555 1550 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
1556 1551 first move C to C1, G to G1, and when it's checking H, the adjusted
1557 1552 destinations will be [C1, G1].
1558 1553
1559 1554 H C1 G1
1560 1555 /| | /
1561 1556 F G |/
1562 1557 K | | -> K
1563 1558 | C D |
1564 1559 | |/ |
1565 1560 | B | ...
1566 1561 |/ |/
1567 1562 A A
1568 1563
1569 1564 Besides, adjust dest according to existing rebase information. For example,
1570 1565
1571 1566 B C D B needs to be rebased on top of C, C needs to be rebased on top
1572 1567 \|/ of D. We will rebase C first.
1573 1568 A
1574 1569
1575 1570 C' After rebasing C, when considering B's destination, use C'
1576 1571 | instead of the original C.
1577 1572 B D
1578 1573 \ /
1579 1574 A
1580 1575 """
1581 1576 # pick already rebased revs with same dest from state as interesting source
1582 1577 dest = destmap[rev]
1583 1578 source = [
1584 1579 s
1585 1580 for s, d in state.items()
1586 1581 if d > 0 and destmap[s] == dest and s not in skipped
1587 1582 ]
1588 1583
1589 1584 result = []
1590 1585 for prev in repo.changelog.parentrevs(rev):
1591 1586 adjusted = dest
1592 1587 if prev != nullrev:
1593 1588 candidate = repo.revs(b'max(%ld and (::%d))', source, prev).first()
1594 1589 if candidate is not None:
1595 1590 adjusted = state[candidate]
1596 1591 if adjusted == dest and dest in state:
1597 1592 adjusted = state[dest]
1598 1593 if adjusted == revtodo:
1599 1594 # sortsource should produce an order that makes this impossible
1600 1595 raise error.ProgrammingError(
1601 1596 b'rev %d should be rebased already at this time' % dest
1602 1597 )
1603 1598 result.append(adjusted)
1604 1599 return result
1605 1600
1606 1601
1607 1602 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
1608 1603 """
1609 1604 Abort if rebase will create divergence or rebase is noop because of markers
1610 1605
1611 1606 `rebaseobsrevs`: set of obsolete revision in source
1612 1607 `rebaseobsskipped`: set of revisions from source skipped because they have
1613 1608 successors in destination or no non-obsolete successor.
1614 1609 """
1615 1610 # Obsolete node with successors not in dest leads to divergence
1616 1611 divergenceok = ui.configbool(b'experimental', b'evolution.allowdivergence')
1617 1612 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
1618 1613
1619 1614 if divergencebasecandidates and not divergenceok:
1620 1615 divhashes = (bytes(repo[r]) for r in divergencebasecandidates)
1621 1616 msg = _(b"this rebase will cause divergences from: %s")
1622 1617 h = _(
1623 1618 b"to force the rebase please set "
1624 1619 b"experimental.evolution.allowdivergence=True"
1625 1620 )
1626 1621 raise error.Abort(msg % (b",".join(divhashes),), hint=h)
1627 1622
1628 1623
1629 1624 def successorrevs(unfi, rev):
1630 1625 """yield revision numbers for successors of rev"""
1631 1626 assert unfi.filtername is None
1632 1627 get_rev = unfi.changelog.index.get_rev
1633 1628 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
1634 1629 r = get_rev(s)
1635 1630 if r is not None:
1636 1631 yield r
1637 1632
1638 1633
1639 1634 def defineparents(repo, rev, destmap, state, skipped, obsskipped):
1640 1635 """Return new parents and optionally a merge base for rev being rebased
1641 1636
1642 1637 The destination specified by "dest" cannot always be used directly because
1643 1638 previously rebase result could affect destination. For example,
1644 1639
1645 1640 D E rebase -r C+D+E -d B
1646 1641 |/ C will be rebased to C'
1647 1642 B C D's new destination will be C' instead of B
1648 1643 |/ E's new destination will be C' instead of B
1649 1644 A
1650 1645
1651 1646 The new parents of a merge is slightly more complicated. See the comment
1652 1647 block below.
1653 1648 """
1654 1649 # use unfiltered changelog since successorrevs may return filtered nodes
1655 1650 assert repo.filtername is None
1656 1651 cl = repo.changelog
1657 1652 isancestor = cl.isancestorrev
1658 1653
1659 1654 dest = destmap[rev]
1660 1655 oldps = repo.changelog.parentrevs(rev) # old parents
1661 1656 newps = [nullrev, nullrev] # new parents
1662 1657 dests = adjustdest(repo, rev, destmap, state, skipped)
1663 1658 bases = list(oldps) # merge base candidates, initially just old parents
1664 1659
1665 1660 if all(r == nullrev for r in oldps[1:]):
1666 1661 # For non-merge changeset, just move p to adjusted dest as requested.
1667 1662 newps[0] = dests[0]
1668 1663 else:
1669 1664 # For merge changeset, if we move p to dests[i] unconditionally, both
1670 1665 # parents may change and the end result looks like "the merge loses a
1671 1666 # parent", which is a surprise. This is a limit because "--dest" only
1672 1667 # accepts one dest per src.
1673 1668 #
1674 1669 # Therefore, only move p with reasonable conditions (in this order):
1675 1670 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1676 1671 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1677 1672 #
1678 1673 # Comparing with adjustdest, the logic here does some additional work:
1679 1674 # 1. decide which parents will not be moved towards dest
1680 1675 # 2. if the above decision is "no", should a parent still be moved
1681 1676 # because it was rebased?
1682 1677 #
1683 1678 # For example:
1684 1679 #
1685 1680 # C # "rebase -r C -d D" is an error since none of the parents
1686 1681 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1687 1682 # A B D # B (using rule "2."), since B will be rebased.
1688 1683 #
1689 1684 # The loop tries to be not rely on the fact that a Mercurial node has
1690 1685 # at most 2 parents.
1691 1686 for i, p in enumerate(oldps):
1692 1687 np = p # new parent
1693 1688 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1694 1689 np = dests[i]
1695 1690 elif p in state and state[p] > 0:
1696 1691 np = state[p]
1697 1692
1698 1693 # "bases" only record "special" merge bases that cannot be
1699 1694 # calculated from changelog DAG (i.e. isancestor(p, np) is False).
1700 1695 # For example:
1701 1696 #
1702 1697 # B' # rebase -s B -d D, when B was rebased to B'. dest for C
1703 1698 # | C # is B', but merge base for C is B, instead of
1704 1699 # D | # changelog.ancestor(C, B') == A. If changelog DAG and
1705 1700 # | B # "state" edges are merged (so there will be an edge from
1706 1701 # |/ # B to B'), the merge base is still ancestor(C, B') in
1707 1702 # A # the merged graph.
1708 1703 #
1709 1704 # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
1710 1705 # which uses "virtual null merge" to explain this situation.
1711 1706 if isancestor(p, np):
1712 1707 bases[i] = nullrev
1713 1708
1714 1709 # If one parent becomes an ancestor of the other, drop the ancestor
1715 1710 for j, x in enumerate(newps[:i]):
1716 1711 if x == nullrev:
1717 1712 continue
1718 1713 if isancestor(np, x): # CASE-1
1719 1714 np = nullrev
1720 1715 elif isancestor(x, np): # CASE-2
1721 1716 newps[j] = np
1722 1717 np = nullrev
1723 1718 # New parents forming an ancestor relationship does not
1724 1719 # mean the old parents have a similar relationship. Do not
1725 1720 # set bases[x] to nullrev.
1726 1721 bases[j], bases[i] = bases[i], bases[j]
1727 1722
1728 1723 newps[i] = np
1729 1724
1730 1725 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1731 1726 # base. If only p2 changes, merging using unchanged p1 as merge base is
1732 1727 # suboptimal. Therefore swap parents to make the merge sane.
1733 1728 if newps[1] != nullrev and oldps[0] == newps[0]:
1734 1729 assert len(newps) == 2 and len(oldps) == 2
1735 1730 newps.reverse()
1736 1731 bases.reverse()
1737 1732
1738 1733 # No parent change might be an error because we fail to make rev a
1739 1734 # descendent of requested dest. This can happen, for example:
1740 1735 #
1741 1736 # C # rebase -r C -d D
1742 1737 # /| # None of A and B will be changed to D and rebase fails.
1743 1738 # A B D
1744 1739 if set(newps) == set(oldps) and dest not in newps:
1745 1740 raise error.Abort(
1746 1741 _(
1747 1742 b'cannot rebase %d:%s without '
1748 1743 b'moving at least one of its parents'
1749 1744 )
1750 1745 % (rev, repo[rev])
1751 1746 )
1752 1747
1753 1748 # Source should not be ancestor of dest. The check here guarantees it's
1754 1749 # impossible. With multi-dest, the initial check does not cover complex
1755 1750 # cases since we don't have abstractions to dry-run rebase cheaply.
1756 1751 if any(p != nullrev and isancestor(rev, p) for p in newps):
1757 1752 raise error.Abort(_(b'source is ancestor of destination'))
1758 1753
1759 1754 # "rebasenode" updates to new p1, use the corresponding merge base.
1760 1755 if bases[0] != nullrev:
1761 1756 base = bases[0]
1762 1757 else:
1763 1758 base = None
1764 1759
1765 1760 # Check if the merge will contain unwanted changes. That may happen if
1766 1761 # there are multiple special (non-changelog ancestor) merge bases, which
1767 1762 # cannot be handled well by the 3-way merge algorithm. For example:
1768 1763 #
1769 1764 # F
1770 1765 # /|
1771 1766 # D E # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
1772 1767 # | | # as merge base, the difference between D and F will include
1773 1768 # B C # C, so the rebased F will contain C surprisingly. If "E" was
1774 1769 # |/ # chosen, the rebased F will contain B.
1775 1770 # A Z
1776 1771 #
1777 1772 # But our merge base candidates (D and E in above case) could still be
1778 1773 # better than the default (ancestor(F, Z) == null). Therefore still
1779 1774 # pick one (so choose p1 above).
1780 1775 if sum(1 for b in set(bases) if b != nullrev) > 1:
1781 1776 unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
1782 1777 for i, base in enumerate(bases):
1783 1778 if base == nullrev:
1784 1779 continue
1785 1780 # Revisions in the side (not chosen as merge base) branch that
1786 1781 # might contain "surprising" contents
1787 1782 siderevs = list(
1788 1783 repo.revs(b'((%ld-%d) %% (%d+%d))', bases, base, base, dest)
1789 1784 )
1790 1785
1791 1786 # If those revisions are covered by rebaseset, the result is good.
1792 1787 # A merge in rebaseset would be considered to cover its ancestors.
1793 1788 if siderevs:
1794 1789 rebaseset = [
1795 1790 r for r, d in state.items() if d > 0 and r not in obsskipped
1796 1791 ]
1797 1792 merges = [
1798 1793 r for r in rebaseset if cl.parentrevs(r)[1] != nullrev
1799 1794 ]
1800 1795 unwanted[i] = list(
1801 1796 repo.revs(
1802 1797 b'%ld - (::%ld) - %ld', siderevs, merges, rebaseset
1803 1798 )
1804 1799 )
1805 1800
1806 1801 # Choose a merge base that has a minimal number of unwanted revs.
1807 1802 l, i = min(
1808 1803 (len(revs), i)
1809 1804 for i, revs in enumerate(unwanted)
1810 1805 if revs is not None
1811 1806 )
1812 1807 base = bases[i]
1813 1808
1814 1809 # newps[0] should match merge base if possible. Currently, if newps[i]
1815 1810 # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
1816 1811 # the other's ancestor. In that case, it's fine to not swap newps here.
1817 1812 # (see CASE-1 and CASE-2 above)
1818 1813 if i != 0 and newps[i] != nullrev:
1819 1814 newps[0], newps[i] = newps[i], newps[0]
1820 1815
1821 1816 # The merge will include unwanted revisions. Abort now. Revisit this if
1822 1817 # we have a more advanced merge algorithm that handles multiple bases.
1823 1818 if l > 0:
1824 1819 unwanteddesc = _(b' or ').join(
1825 1820 (
1826 1821 b', '.join(b'%d:%s' % (r, repo[r]) for r in revs)
1827 1822 for revs in unwanted
1828 1823 if revs is not None
1829 1824 )
1830 1825 )
1831 1826 raise error.Abort(
1832 1827 _(b'rebasing %d:%s will include unwanted changes from %s')
1833 1828 % (rev, repo[rev], unwanteddesc)
1834 1829 )
1835 1830
1836 1831 repo.ui.debug(b" future parents are %d and %d\n" % tuple(newps))
1837 1832
1838 1833 return newps[0], newps[1], base
1839 1834
1840 1835
1841 1836 def isagitpatch(repo, patchname):
1842 1837 """Return true if the given patch is in git format"""
1843 1838 mqpatch = os.path.join(repo.mq.path, patchname)
1844 1839 for line in patch.linereader(open(mqpatch, b'rb')):
1845 1840 if line.startswith(b'diff --git'):
1846 1841 return True
1847 1842 return False
1848 1843
1849 1844
1850 1845 def updatemq(repo, state, skipped, **opts):
1851 1846 """Update rebased mq patches - finalize and then import them"""
1852 1847 mqrebase = {}
1853 1848 mq = repo.mq
1854 1849 original_series = mq.fullseries[:]
1855 1850 skippedpatches = set()
1856 1851
1857 1852 for p in mq.applied:
1858 1853 rev = repo[p.node].rev()
1859 1854 if rev in state:
1860 1855 repo.ui.debug(
1861 1856 b'revision %d is an mq patch (%s), finalize it.\n'
1862 1857 % (rev, p.name)
1863 1858 )
1864 1859 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1865 1860 else:
1866 1861 # Applied but not rebased, not sure this should happen
1867 1862 skippedpatches.add(p.name)
1868 1863
1869 1864 if mqrebase:
1870 1865 mq.finish(repo, mqrebase.keys())
1871 1866
1872 1867 # We must start import from the newest revision
1873 1868 for rev in sorted(mqrebase, reverse=True):
1874 1869 if rev not in skipped:
1875 1870 name, isgit = mqrebase[rev]
1876 1871 repo.ui.note(
1877 1872 _(b'updating mq patch %s to %d:%s\n')
1878 1873 % (name, state[rev], repo[state[rev]])
1879 1874 )
1880 1875 mq.qimport(
1881 1876 repo,
1882 1877 (),
1883 1878 patchname=name,
1884 1879 git=isgit,
1885 1880 rev=[b"%d" % state[rev]],
1886 1881 )
1887 1882 else:
1888 1883 # Rebased and skipped
1889 1884 skippedpatches.add(mqrebase[rev][0])
1890 1885
1891 1886 # Patches were either applied and rebased and imported in
1892 1887 # order, applied and removed or unapplied. Discard the removed
1893 1888 # ones while preserving the original series order and guards.
1894 1889 newseries = [
1895 1890 s
1896 1891 for s in original_series
1897 1892 if mq.guard_re.split(s, 1)[0] not in skippedpatches
1898 1893 ]
1899 1894 mq.fullseries[:] = newseries
1900 1895 mq.seriesdirty = True
1901 1896 mq.savedirty()
1902 1897
1903 1898
1904 1899 def storecollapsemsg(repo, collapsemsg):
1905 1900 """Store the collapse message to allow recovery"""
1906 1901 collapsemsg = collapsemsg or b''
1907 1902 f = repo.vfs(b"last-message.txt", b"w")
1908 1903 f.write(b"%s\n" % collapsemsg)
1909 1904 f.close()
1910 1905
1911 1906
1912 1907 def clearcollapsemsg(repo):
1913 1908 """Remove collapse message file"""
1914 1909 repo.vfs.unlinkpath(b"last-message.txt", ignoremissing=True)
1915 1910
1916 1911
1917 1912 def restorecollapsemsg(repo, isabort):
1918 1913 """Restore previously stored collapse message"""
1919 1914 try:
1920 1915 f = repo.vfs(b"last-message.txt")
1921 1916 collapsemsg = f.readline().strip()
1922 1917 f.close()
1923 1918 except IOError as err:
1924 1919 if err.errno != errno.ENOENT:
1925 1920 raise
1926 1921 if isabort:
1927 1922 # Oh well, just abort like normal
1928 1923 collapsemsg = b''
1929 1924 else:
1930 1925 raise error.Abort(_(b'missing .hg/last-message.txt for rebase'))
1931 1926 return collapsemsg
1932 1927
1933 1928
1934 1929 def clearstatus(repo):
1935 1930 """Remove the status files"""
1936 1931 # Make sure the active transaction won't write the state file
1937 1932 tr = repo.currenttransaction()
1938 1933 if tr:
1939 1934 tr.removefilegenerator(b'rebasestate')
1940 1935 repo.vfs.unlinkpath(b"rebasestate", ignoremissing=True)
1941 1936
1942 1937
1943 1938 def needupdate(repo, state):
1944 1939 '''check whether we should `update --clean` away from a merge, or if
1945 1940 somehow the working dir got forcibly updated, e.g. by older hg'''
1946 1941 parents = [p.rev() for p in repo[None].parents()]
1947 1942
1948 1943 # Are we in a merge state at all?
1949 1944 if len(parents) < 2:
1950 1945 return False
1951 1946
1952 1947 # We should be standing on the first as-of-yet unrebased commit.
1953 1948 firstunrebased = min(
1954 1949 [old for old, new in pycompat.iteritems(state) if new == nullrev]
1955 1950 )
1956 1951 if firstunrebased in parents:
1957 1952 return True
1958 1953
1959 1954 return False
1960 1955
1961 1956
1962 1957 def sortsource(destmap):
1963 1958 """yield source revisions in an order that we only rebase things once
1964 1959
1965 1960 If source and destination overlaps, we should filter out revisions
1966 1961 depending on other revisions which hasn't been rebased yet.
1967 1962
1968 1963 Yield a sorted list of revisions each time.
1969 1964
1970 1965 For example, when rebasing A to B, B to C. This function yields [B], then
1971 1966 [A], indicating B needs to be rebased first.
1972 1967
1973 1968 Raise if there is a cycle so the rebase is impossible.
1974 1969 """
1975 1970 srcset = set(destmap)
1976 1971 while srcset:
1977 1972 srclist = sorted(srcset)
1978 1973 result = []
1979 1974 for r in srclist:
1980 1975 if destmap[r] not in srcset:
1981 1976 result.append(r)
1982 1977 if not result:
1983 1978 raise error.Abort(_(b'source and destination form a cycle'))
1984 1979 srcset -= set(result)
1985 1980 yield result
1986 1981
1987 1982
1988 1983 def buildstate(repo, destmap, collapse):
1989 1984 '''Define which revisions are going to be rebased and where
1990 1985
1991 1986 repo: repo
1992 1987 destmap: {srcrev: destrev}
1993 1988 '''
1994 1989 rebaseset = destmap.keys()
1995 1990 originalwd = repo[b'.'].rev()
1996 1991
1997 1992 # This check isn't strictly necessary, since mq detects commits over an
1998 1993 # applied patch. But it prevents messing up the working directory when
1999 1994 # a partially completed rebase is blocked by mq.
2000 1995 if b'qtip' in repo.tags():
2001 1996 mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
2002 1997 if set(destmap.values()) & mqapplied:
2003 1998 raise error.Abort(_(b'cannot rebase onto an applied mq patch'))
2004 1999
2005 2000 # Get "cycle" error early by exhausting the generator.
2006 2001 sortedsrc = list(sortsource(destmap)) # a list of sorted revs
2007 2002 if not sortedsrc:
2008 2003 raise error.Abort(_(b'no matching revisions'))
2009 2004
2010 2005 # Only check the first batch of revisions to rebase not depending on other
2011 2006 # rebaseset. This means "source is ancestor of destination" for the second
2012 2007 # (and following) batches of revisions are not checked here. We rely on
2013 2008 # "defineparents" to do that check.
2014 2009 roots = list(repo.set(b'roots(%ld)', sortedsrc[0]))
2015 2010 if not roots:
2016 2011 raise error.Abort(_(b'no matching revisions'))
2017 2012
2018 2013 def revof(r):
2019 2014 return r.rev()
2020 2015
2021 2016 roots = sorted(roots, key=revof)
2022 2017 state = dict.fromkeys(rebaseset, revtodo)
2023 2018 emptyrebase = len(sortedsrc) == 1
2024 2019 for root in roots:
2025 2020 dest = repo[destmap[root.rev()]]
2026 2021 commonbase = root.ancestor(dest)
2027 2022 if commonbase == root:
2028 2023 raise error.Abort(_(b'source is ancestor of destination'))
2029 2024 if commonbase == dest:
2030 2025 wctx = repo[None]
2031 2026 if dest == wctx.p1():
2032 2027 # when rebasing to '.', it will use the current wd branch name
2033 2028 samebranch = root.branch() == wctx.branch()
2034 2029 else:
2035 2030 samebranch = root.branch() == dest.branch()
2036 2031 if not collapse and samebranch and dest in root.parents():
2037 2032 # mark the revision as done by setting its new revision
2038 2033 # equal to its old (current) revisions
2039 2034 state[root.rev()] = root.rev()
2040 2035 repo.ui.debug(b'source is a child of destination\n')
2041 2036 continue
2042 2037
2043 2038 emptyrebase = False
2044 2039 repo.ui.debug(b'rebase onto %s starting from %s\n' % (dest, root))
2045 2040 if emptyrebase:
2046 2041 return None
2047 2042 for rev in sorted(state):
2048 2043 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
2049 2044 # if all parents of this revision are done, then so is this revision
2050 2045 if parents and all((state.get(p) == p for p in parents)):
2051 2046 state[rev] = rev
2052 2047 return originalwd, destmap, state
2053 2048
2054 2049
2055 2050 def clearrebased(
2056 2051 ui,
2057 2052 repo,
2058 2053 destmap,
2059 2054 state,
2060 2055 skipped,
2061 2056 collapsedas=None,
2062 2057 keepf=False,
2063 2058 fm=None,
2064 2059 backup=True,
2065 2060 ):
2066 2061 """dispose of rebased revision at the end of the rebase
2067 2062
2068 2063 If `collapsedas` is not None, the rebase was a collapse whose result if the
2069 2064 `collapsedas` node.
2070 2065
2071 2066 If `keepf` is not True, the rebase has --keep set and no nodes should be
2072 2067 removed (but bookmarks still need to be moved).
2073 2068
2074 2069 If `backup` is False, no backup will be stored when stripping rebased
2075 2070 revisions.
2076 2071 """
2077 2072 tonode = repo.changelog.node
2078 2073 replacements = {}
2079 2074 moves = {}
2080 2075 stripcleanup = not obsolete.isenabled(repo, obsolete.createmarkersopt)
2081 2076
2082 2077 collapsednodes = []
2083 2078 for rev, newrev in sorted(state.items()):
2084 2079 if newrev >= 0 and newrev != rev:
2085 2080 oldnode = tonode(rev)
2086 2081 newnode = collapsedas or tonode(newrev)
2087 2082 moves[oldnode] = newnode
2088 2083 succs = None
2089 2084 if rev in skipped:
2090 2085 if stripcleanup or not repo[rev].obsolete():
2091 2086 succs = ()
2092 2087 elif collapsedas:
2093 2088 collapsednodes.append(oldnode)
2094 2089 else:
2095 2090 succs = (newnode,)
2096 2091 if succs is not None:
2097 2092 replacements[(oldnode,)] = succs
2098 2093 if collapsednodes:
2099 2094 replacements[tuple(collapsednodes)] = (collapsedas,)
2100 2095 if fm:
2101 2096 hf = fm.hexfunc
2102 2097 fl = fm.formatlist
2103 2098 fd = fm.formatdict
2104 2099 changes = {}
2105 2100 for oldns, newn in pycompat.iteritems(replacements):
2106 2101 for oldn in oldns:
2107 2102 changes[hf(oldn)] = fl([hf(n) for n in newn], name=b'node')
2108 2103 nodechanges = fd(changes, key=b"oldnode", value=b"newnodes")
2109 2104 fm.data(nodechanges=nodechanges)
2110 2105 if keepf:
2111 2106 replacements = {}
2112 2107 scmutil.cleanupnodes(repo, replacements, b'rebase', moves, backup=backup)
2113 2108
2114 2109
2115 2110 def pullrebase(orig, ui, repo, *args, **opts):
2116 2111 """Call rebase after pull if the latter has been invoked with --rebase"""
2117 2112 if opts.get('rebase'):
2118 2113 if ui.configbool(b'commands', b'rebase.requiredest'):
2119 2114 msg = _(b'rebase destination required by configuration')
2120 2115 hint = _(b'use hg pull followed by hg rebase -d DEST')
2121 2116 raise error.Abort(msg, hint=hint)
2122 2117
2123 2118 with repo.wlock(), repo.lock():
2124 2119 if opts.get('update'):
2125 2120 del opts['update']
2126 2121 ui.debug(
2127 2122 b'--update and --rebase are not compatible, ignoring '
2128 2123 b'the update flag\n'
2129 2124 )
2130 2125
2131 2126 cmdutil.checkunfinished(repo, skipmerge=True)
2132 2127 cmdutil.bailifchanged(
2133 2128 repo,
2134 2129 hint=_(
2135 2130 b'cannot pull with rebase: '
2136 2131 b'please commit or shelve your changes first'
2137 2132 ),
2138 2133 )
2139 2134
2140 2135 revsprepull = len(repo)
2141 2136 origpostincoming = commands.postincoming
2142 2137
2143 2138 def _dummy(*args, **kwargs):
2144 2139 pass
2145 2140
2146 2141 commands.postincoming = _dummy
2147 2142 try:
2148 2143 ret = orig(ui, repo, *args, **opts)
2149 2144 finally:
2150 2145 commands.postincoming = origpostincoming
2151 2146 revspostpull = len(repo)
2152 2147 if revspostpull > revsprepull:
2153 2148 # --rev option from pull conflict with rebase own --rev
2154 2149 # dropping it
2155 2150 if 'rev' in opts:
2156 2151 del opts['rev']
2157 2152 # positional argument from pull conflicts with rebase's own
2158 2153 # --source.
2159 2154 if 'source' in opts:
2160 2155 del opts['source']
2161 2156 # revsprepull is the len of the repo, not revnum of tip.
2162 2157 destspace = list(repo.changelog.revs(start=revsprepull))
2163 2158 opts['_destspace'] = destspace
2164 2159 try:
2165 2160 rebase(ui, repo, **opts)
2166 2161 except error.NoMergeDestAbort:
2167 2162 # we can maybe update instead
2168 2163 rev, _a, _b = destutil.destupdate(repo)
2169 2164 if rev == repo[b'.'].rev():
2170 2165 ui.status(_(b'nothing to rebase\n'))
2171 2166 else:
2172 2167 ui.status(_(b'nothing to rebase - updating instead\n'))
2173 2168 # not passing argument to get the bare update behavior
2174 2169 # with warning and trumpets
2175 2170 commands.update(ui, repo)
2176 2171 else:
2177 2172 if opts.get('tool'):
2178 2173 raise error.Abort(_(b'--tool can only be used with --rebase'))
2179 2174 ret = orig(ui, repo, *args, **opts)
2180 2175
2181 2176 return ret
2182 2177
2183 2178
2184 2179 def _filterobsoleterevs(repo, revs):
2185 2180 """returns a set of the obsolete revisions in revs"""
2186 2181 return set(r for r in revs if repo[r].obsolete())
2187 2182
2188 2183
2189 2184 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
2190 2185 """Return (obsoletenotrebased, obsoletewithoutsuccessorindestination).
2191 2186
2192 2187 `obsoletenotrebased` is a mapping mapping obsolete => successor for all
2193 2188 obsolete nodes to be rebased given in `rebaseobsrevs`.
2194 2189
2195 2190 `obsoletewithoutsuccessorindestination` is a set with obsolete revisions
2196 2191 without a successor in destination.
2197 2192
2198 2193 `obsoleteextinctsuccessors` is a set of obsolete revisions with only
2199 2194 obsolete successors.
2200 2195 """
2201 2196 obsoletenotrebased = {}
2202 2197 obsoletewithoutsuccessorindestination = set()
2203 2198 obsoleteextinctsuccessors = set()
2204 2199
2205 2200 assert repo.filtername is None
2206 2201 cl = repo.changelog
2207 2202 get_rev = cl.index.get_rev
2208 2203 extinctrevs = set(repo.revs(b'extinct()'))
2209 2204 for srcrev in rebaseobsrevs:
2210 2205 srcnode = cl.node(srcrev)
2211 2206 # XXX: more advanced APIs are required to handle split correctly
2212 2207 successors = set(obsutil.allsuccessors(repo.obsstore, [srcnode]))
2213 2208 # obsutil.allsuccessors includes node itself
2214 2209 successors.remove(srcnode)
2215 2210 succrevs = {get_rev(s) for s in successors}
2216 2211 succrevs.discard(None)
2217 2212 if succrevs.issubset(extinctrevs):
2218 2213 # all successors are extinct
2219 2214 obsoleteextinctsuccessors.add(srcrev)
2220 2215 if not successors:
2221 2216 # no successor
2222 2217 obsoletenotrebased[srcrev] = None
2223 2218 else:
2224 2219 dstrev = destmap[srcrev]
2225 2220 for succrev in succrevs:
2226 2221 if cl.isancestorrev(succrev, dstrev):
2227 2222 obsoletenotrebased[srcrev] = succrev
2228 2223 break
2229 2224 else:
2230 2225 # If 'srcrev' has a successor in rebase set but none in
2231 2226 # destination (which would be catched above), we shall skip it
2232 2227 # and its descendants to avoid divergence.
2233 2228 if srcrev in extinctrevs or any(s in destmap for s in succrevs):
2234 2229 obsoletewithoutsuccessorindestination.add(srcrev)
2235 2230
2236 2231 return (
2237 2232 obsoletenotrebased,
2238 2233 obsoletewithoutsuccessorindestination,
2239 2234 obsoleteextinctsuccessors,
2240 2235 )
2241 2236
2242 2237
2243 2238 def abortrebase(ui, repo):
2244 2239 with repo.wlock(), repo.lock():
2245 2240 rbsrt = rebaseruntime(repo, ui)
2246 2241 rbsrt._prepareabortorcontinue(isabort=True)
2247 2242
2248 2243
2249 2244 def continuerebase(ui, repo):
2250 2245 with repo.wlock(), repo.lock():
2251 2246 rbsrt = rebaseruntime(repo, ui)
2252 2247 ms = mergemod.mergestate.read(repo)
2253 2248 mergeutil.checkunresolved(ms)
2254 2249 retcode = rbsrt._prepareabortorcontinue(isabort=False)
2255 2250 if retcode is not None:
2256 2251 return retcode
2257 2252 rbsrt._performrebase(None)
2258 2253 rbsrt._finishrebase()
2259 2254
2260 2255
2261 2256 def summaryhook(ui, repo):
2262 2257 if not repo.vfs.exists(b'rebasestate'):
2263 2258 return
2264 2259 try:
2265 2260 rbsrt = rebaseruntime(repo, ui, {})
2266 2261 rbsrt.restorestatus()
2267 2262 state = rbsrt.state
2268 2263 except error.RepoLookupError:
2269 2264 # i18n: column positioning for "hg summary"
2270 2265 msg = _(b'rebase: (use "hg rebase --abort" to clear broken state)\n')
2271 2266 ui.write(msg)
2272 2267 return
2273 2268 numrebased = len([i for i in pycompat.itervalues(state) if i >= 0])
2274 2269 # i18n: column positioning for "hg summary"
2275 2270 ui.write(
2276 2271 _(b'rebase: %s, %s (rebase --continue)\n')
2277 2272 % (
2278 2273 ui.label(_(b'%d rebased'), b'rebase.rebased') % numrebased,
2279 2274 ui.label(_(b'%d remaining'), b'rebase.remaining')
2280 2275 % (len(state) - numrebased),
2281 2276 )
2282 2277 )
2283 2278
2284 2279
2285 2280 def uisetup(ui):
2286 2281 # Replace pull with a decorator to provide --rebase option
2287 2282 entry = extensions.wrapcommand(commands.table, b'pull', pullrebase)
2288 2283 entry[1].append(
2289 2284 (b'', b'rebase', None, _(b"rebase working directory to branch head"))
2290 2285 )
2291 2286 entry[1].append((b't', b'tool', b'', _(b"specify merge tool for rebase")))
2292 2287 cmdutil.summaryhooks.add(b'rebase', summaryhook)
2293 2288 statemod.addunfinished(
2294 2289 b'rebase',
2295 2290 fname=b'rebasestate',
2296 2291 stopflag=True,
2297 2292 continueflag=True,
2298 2293 abortfunc=abortrebase,
2299 2294 continuefunc=continuerebase,
2300 2295 )
General Comments 0
You need to be logged in to leave comments. Login now