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