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