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