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