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