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