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