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