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