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