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