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