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