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