##// END OF EJS Templates
rebase: move working parent and bookmark for obsoleted revs (BC)...
Jun Wu -
r34010:9422107a default
parent child Browse files
Show More
@@ -1,1667 +1,1674
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 nullid,
25 25 nullrev,
26 26 short,
27 27 )
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 lock,
39 39 merge as mergemod,
40 40 mergeutil,
41 41 obsolete,
42 42 obsutil,
43 43 patch,
44 44 phases,
45 45 registrar,
46 46 repair,
47 47 repoview,
48 48 revset,
49 49 revsetlang,
50 50 scmutil,
51 51 smartset,
52 52 util,
53 53 )
54 54
55 55 release = lock.release
56 56 templateopts = cmdutil.templateopts
57 57
58 58 # The following constants are used throughout the rebase module. The ordering of
59 59 # their values must be maintained.
60 60
61 61 # Indicates that a revision needs to be rebased
62 62 revtodo = -1
63 63 revtodostr = '-1'
64 64
65 65 # legacy revstates no longer needed in current code
66 66 # -2: nullmerge, -3: revignored, -4: revprecursor, -5: revpruned
67 67 legacystates = {'-2', '-3', '-4', '-5'}
68 68
69 69 cmdtable = {}
70 70 command = registrar.command(cmdtable)
71 71 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
72 72 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
73 73 # be specifying the version(s) of Mercurial they are tested with, or
74 74 # leave the attribute unspecified.
75 75 testedwith = 'ships-with-hg-core'
76 76
77 77 def _nothingtorebase():
78 78 return 1
79 79
80 80 def _savegraft(ctx, extra):
81 81 s = ctx.extra().get('source', None)
82 82 if s is not None:
83 83 extra['source'] = s
84 84 s = ctx.extra().get('intermediate-source', None)
85 85 if s is not None:
86 86 extra['intermediate-source'] = s
87 87
88 88 def _savebranch(ctx, extra):
89 89 extra['branch'] = ctx.branch()
90 90
91 91 def _makeextrafn(copiers):
92 92 """make an extrafn out of the given copy-functions.
93 93
94 94 A copy function takes a context and an extra dict, and mutates the
95 95 extra dict as needed based on the given context.
96 96 """
97 97 def extrafn(ctx, extra):
98 98 for c in copiers:
99 99 c(ctx, extra)
100 100 return extrafn
101 101
102 102 def _destrebase(repo, sourceset, destspace=None):
103 103 """small wrapper around destmerge to pass the right extra args
104 104
105 105 Please wrap destutil.destmerge instead."""
106 106 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
107 107 onheadcheck=False, destspace=destspace)
108 108
109 109 revsetpredicate = registrar.revsetpredicate()
110 110
111 111 @revsetpredicate('_destrebase')
112 112 def _revsetdestrebase(repo, subset, x):
113 113 # ``_rebasedefaultdest()``
114 114
115 115 # default destination for rebase.
116 116 # # XXX: Currently private because I expect the signature to change.
117 117 # # XXX: - bailing out in case of ambiguity vs returning all data.
118 118 # i18n: "_rebasedefaultdest" is a keyword
119 119 sourceset = None
120 120 if x is not None:
121 121 sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
122 122 return subset & smartset.baseset([_destrebase(repo, sourceset)])
123 123
124 124 def _ctxdesc(ctx):
125 125 """short description for a context"""
126 126 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
127 127 ctx.description().split('\n', 1)[0])
128 128 repo = ctx.repo()
129 129 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
130 130 if names:
131 131 desc += ' (%s)' % ' '.join(names)
132 132 return desc
133 133
134 134 class rebaseruntime(object):
135 135 """This class is a container for rebase runtime state"""
136 136 def __init__(self, repo, ui, opts=None):
137 137 if opts is None:
138 138 opts = {}
139 139
140 140 self.repo = repo
141 141 self.ui = ui
142 142 self.opts = opts
143 143 self.originalwd = None
144 144 self.external = nullrev
145 145 # Mapping between the old revision id and either what is the new rebased
146 146 # revision or what needs to be done with the old revision. The state
147 147 # dict will be what contains most of the rebase progress state.
148 148 self.state = {}
149 149 self.activebookmark = None
150 150 self.destmap = {}
151 151 self.skipped = set()
152 152
153 153 self.collapsef = opts.get('collapse', False)
154 154 self.collapsemsg = cmdutil.logmessage(ui, opts)
155 155 self.date = opts.get('date', None)
156 156
157 157 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
158 158 self.extrafns = [_savegraft]
159 159 if e:
160 160 self.extrafns = [e]
161 161
162 162 self.keepf = opts.get('keep', False)
163 163 self.keepbranchesf = opts.get('keepbranches', False)
164 164 # keepopen is not meant for use on the command line, but by
165 165 # other extensions
166 166 self.keepopen = opts.get('keepopen', False)
167 167 self.obsoletenotrebased = {}
168 168
169 169 def storestatus(self, tr=None):
170 170 """Store the current status to allow recovery"""
171 171 if tr:
172 172 tr.addfilegenerator('rebasestate', ('rebasestate',),
173 173 self._writestatus, location='plain')
174 174 else:
175 175 with self.repo.vfs("rebasestate", "w") as f:
176 176 self._writestatus(f)
177 177
178 178 def _writestatus(self, f):
179 179 repo = self.repo.unfiltered()
180 180 f.write(repo[self.originalwd].hex() + '\n')
181 181 # was "dest". we now write dest per src root below.
182 182 f.write('\n')
183 183 f.write(repo[self.external].hex() + '\n')
184 184 f.write('%d\n' % int(self.collapsef))
185 185 f.write('%d\n' % int(self.keepf))
186 186 f.write('%d\n' % int(self.keepbranchesf))
187 187 f.write('%s\n' % (self.activebookmark or ''))
188 188 destmap = self.destmap
189 189 for d, v in self.state.iteritems():
190 190 oldrev = repo[d].hex()
191 191 if v >= 0:
192 192 newrev = repo[v].hex()
193 193 else:
194 194 newrev = v
195 195 destnode = repo[destmap[d]].hex()
196 196 f.write("%s:%s:%s\n" % (oldrev, newrev, destnode))
197 197 repo.ui.debug('rebase status stored\n')
198 198
199 199 def restorestatus(self):
200 200 """Restore a previously stored status"""
201 201 repo = self.repo
202 202 keepbranches = None
203 203 legacydest = None
204 204 collapse = False
205 205 external = nullrev
206 206 activebookmark = None
207 207 state = {}
208 208 destmap = {}
209 209
210 210 try:
211 211 f = repo.vfs("rebasestate")
212 212 for i, l in enumerate(f.read().splitlines()):
213 213 if i == 0:
214 214 originalwd = repo[l].rev()
215 215 elif i == 1:
216 216 # this line should be empty in newer version. but legacy
217 217 # clients may still use it
218 218 if l:
219 219 legacydest = repo[l].rev()
220 220 elif i == 2:
221 221 external = repo[l].rev()
222 222 elif i == 3:
223 223 collapse = bool(int(l))
224 224 elif i == 4:
225 225 keep = bool(int(l))
226 226 elif i == 5:
227 227 keepbranches = bool(int(l))
228 228 elif i == 6 and not (len(l) == 81 and ':' in l):
229 229 # line 6 is a recent addition, so for backwards
230 230 # compatibility check that the line doesn't look like the
231 231 # oldrev:newrev lines
232 232 activebookmark = l
233 233 else:
234 234 args = l.split(':')
235 235 oldrev = args[0]
236 236 newrev = args[1]
237 237 if newrev in legacystates:
238 238 continue
239 239 if len(args) > 2:
240 240 destnode = args[2]
241 241 else:
242 242 destnode = legacydest
243 243 destmap[repo[oldrev].rev()] = repo[destnode].rev()
244 244 if newrev in (nullid, revtodostr):
245 245 state[repo[oldrev].rev()] = revtodo
246 246 # Legacy compat special case
247 247 else:
248 248 state[repo[oldrev].rev()] = repo[newrev].rev()
249 249
250 250 except IOError as err:
251 251 if err.errno != errno.ENOENT:
252 252 raise
253 253 cmdutil.wrongtooltocontinue(repo, _('rebase'))
254 254
255 255 if keepbranches is None:
256 256 raise error.Abort(_('.hg/rebasestate is incomplete'))
257 257
258 258 skipped = set()
259 259 # recompute the set of skipped revs
260 260 if not collapse:
261 261 seen = set(destmap.values())
262 262 for old, new in sorted(state.items()):
263 263 if new != revtodo and new in seen:
264 264 skipped.add(old)
265 265 seen.add(new)
266 266 repo.ui.debug('computed skipped revs: %s\n' %
267 267 (' '.join(str(r) for r in sorted(skipped)) or None))
268 268 repo.ui.debug('rebase status resumed\n')
269 269 _setrebasesetvisibility(repo, set(state.keys()) | {originalwd})
270 270
271 271 self.originalwd = originalwd
272 272 self.destmap = destmap
273 273 self.state = state
274 274 self.skipped = skipped
275 275 self.collapsef = collapse
276 276 self.keepf = keep
277 277 self.keepbranchesf = keepbranches
278 278 self.external = external
279 279 self.activebookmark = activebookmark
280 280
281 281 def _handleskippingobsolete(self, obsoleterevs, destmap):
282 282 """Compute structures necessary for skipping obsolete revisions
283 283
284 284 obsoleterevs: iterable of all obsolete revisions in rebaseset
285 285 destmap: {srcrev: destrev} destination revisions
286 286 """
287 287 self.obsoletenotrebased = {}
288 288 if not self.ui.configbool('experimental', 'rebaseskipobsolete',
289 289 default=True):
290 290 return
291 291 obsoleteset = set(obsoleterevs)
292 292 self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
293 293 obsoleteset, destmap)
294 294 skippedset = set(self.obsoletenotrebased)
295 295 _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
296 296
297 297 def _prepareabortorcontinue(self, isabort):
298 298 try:
299 299 self.restorestatus()
300 300 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
301 301 except error.RepoLookupError:
302 302 if isabort:
303 303 clearstatus(self.repo)
304 304 clearcollapsemsg(self.repo)
305 305 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
306 306 ' only broken state is cleared)\n'))
307 307 return 0
308 308 else:
309 309 msg = _('cannot continue inconsistent rebase')
310 310 hint = _('use "hg rebase --abort" to clear broken state')
311 311 raise error.Abort(msg, hint=hint)
312 312 if isabort:
313 313 return abort(self.repo, self.originalwd, self.destmap,
314 314 self.state, activebookmark=self.activebookmark)
315 315
316 316 def _preparenewrebase(self, destmap):
317 317 if not destmap:
318 318 return _nothingtorebase()
319 319
320 320 rebaseset = destmap.keys()
321 321 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
322 322 if (not (self.keepf or allowunstable)
323 323 and self.repo.revs('first(children(%ld) - %ld)',
324 324 rebaseset, rebaseset)):
325 325 raise error.Abort(
326 326 _("can't remove original changesets with"
327 327 " unrebased descendants"),
328 328 hint=_('use --keep to keep original changesets'))
329 329
330 obsrevs = _filterobsoleterevs(self.repo, rebaseset)
331 self._handleskippingobsolete(obsrevs, destmap)
332
333 result = buildstate(self.repo, destmap, self.collapsef,
334 self.obsoletenotrebased)
330 result = buildstate(self.repo, destmap, self.collapsef)
335 331
336 332 if not result:
337 333 # Empty state built, nothing to rebase
338 334 self.ui.status(_('nothing to rebase\n'))
339 335 return _nothingtorebase()
340 336
341 337 for root in self.repo.set('roots(%ld)', rebaseset):
342 338 if not self.keepf and not root.mutable():
343 339 raise error.Abort(_("can't rebase public changeset %s")
344 340 % root,
345 341 hint=_("see 'hg help phases' for details"))
346 342
347 343 (self.originalwd, self.destmap, self.state) = result
348 344 if self.collapsef:
349 345 dests = set(self.destmap.values())
350 346 if len(dests) != 1:
351 347 raise error.Abort(
352 348 _('--collapse does not work with multiple destinations'))
353 349 destrev = next(iter(dests))
354 350 destancestors = self.repo.changelog.ancestors([destrev],
355 351 inclusive=True)
356 352 self.external = externalparent(self.repo, self.state, destancestors)
357 353
358 354 for destrev in sorted(set(destmap.values())):
359 355 dest = self.repo[destrev]
360 356 if dest.closesbranch() and not self.keepbranchesf:
361 357 self.ui.status(_('reopening closed branch head %s\n') % dest)
362 358
363 359 def _performrebase(self, tr):
364 360 repo, ui = self.repo, self.ui
365 361 if self.keepbranchesf:
366 362 # insert _savebranch at the start of extrafns so if
367 363 # there's a user-provided extrafn it can clobber branch if
368 364 # desired
369 365 self.extrafns.insert(0, _savebranch)
370 366 if self.collapsef:
371 367 branches = set()
372 368 for rev in self.state:
373 369 branches.add(repo[rev].branch())
374 370 if len(branches) > 1:
375 371 raise error.Abort(_('cannot collapse multiple named '
376 372 'branches'))
377 373
374 # Calculate self.obsoletenotrebased
375 obsrevs = _filterobsoleterevs(self.repo, self.state)
376 self._handleskippingobsolete(obsrevs, self.destmap)
377
378 378 # Keep track of the active bookmarks in order to reset them later
379 379 self.activebookmark = self.activebookmark or repo._activebookmark
380 380 if self.activebookmark:
381 381 bookmarks.deactivate(repo)
382 382
383 383 # Store the state before we begin so users can run 'hg rebase --abort'
384 384 # if we fail before the transaction closes.
385 385 self.storestatus()
386 386
387 387 cands = [k for k, v in self.state.iteritems() if v == revtodo]
388 388 total = len(cands)
389 389 pos = 0
390 390 for subset in sortsource(self.destmap):
391 391 pos = self._performrebasesubset(tr, subset, pos, total)
392 392 ui.progress(_('rebasing'), None)
393 393 ui.note(_('rebase merging completed\n'))
394 394
395 395 def _performrebasesubset(self, tr, subset, pos, total):
396 396 repo, ui, opts = self.repo, self.ui, self.opts
397 397 sortedrevs = repo.revs('sort(%ld, -topo)', subset)
398 398 for rev in sortedrevs:
399 399 dest = self.destmap[rev]
400 400 ctx = repo[rev]
401 401 desc = _ctxdesc(ctx)
402 402 if self.state[rev] == rev:
403 403 ui.status(_('already rebased %s\n') % desc)
404 elif rev in self.obsoletenotrebased:
405 succ = self.obsoletenotrebased[rev]
406 if succ is None:
407 msg = _('note: not rebasing %s, it has no '
408 'successor\n') % desc
409 else:
410 succctx = repo[succ]
411 succdesc = '%d:%s "%s"' % (
412 succctx.rev(), succctx,
413 succctx.description().split('\n', 1)[0])
414 msg = (_('note: not rebasing %s, already in '
415 'destination as %s\n') % (desc, succdesc))
416 repo.ui.status(msg)
417 # Make clearrebased aware state[rev] is not a true successor
418 self.skipped.add(rev)
419 # Record rev as moved to its desired destination in self.state.
420 # This helps bookmark and working parent movement.
421 dest = max(adjustdest(repo, rev, self.destmap, self.state,
422 self.skipped))
423 self.state[rev] = dest
404 424 elif self.state[rev] == revtodo:
405 425 pos += 1
406 426 ui.status(_('rebasing %s\n') % desc)
407 427 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
408 428 _('changesets'), total)
409 429 p1, p2, base = defineparents(repo, rev, self.destmap,
410 self.state)
430 self.state, self.skipped,
431 self.obsoletenotrebased)
411 432 self.storestatus(tr=tr)
412 433 storecollapsemsg(repo, self.collapsemsg)
413 434 if len(repo[None].parents()) == 2:
414 435 repo.ui.debug('resuming interrupted rebase\n')
415 436 else:
416 437 try:
417 438 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
418 439 'rebase')
419 440 stats = rebasenode(repo, rev, p1, base, self.state,
420 441 self.collapsef, dest)
421 442 if stats and stats[3] > 0:
422 443 raise error.InterventionRequired(
423 444 _('unresolved conflicts (see hg '
424 445 'resolve, then hg rebase --continue)'))
425 446 finally:
426 447 ui.setconfig('ui', 'forcemerge', '', 'rebase')
427 448 if not self.collapsef:
428 449 merging = p2 != nullrev
429 450 editform = cmdutil.mergeeditform(merging, 'rebase')
430 451 editor = cmdutil.getcommiteditor(editform=editform, **opts)
431 452 newnode = concludenode(repo, rev, p1, p2,
432 453 extrafn=_makeextrafn(self.extrafns),
433 454 editor=editor,
434 455 keepbranches=self.keepbranchesf,
435 456 date=self.date)
436 457 if newnode is None:
437 458 # If it ended up being a no-op commit, then the normal
438 459 # merge state clean-up path doesn't happen, so do it
439 460 # here. Fix issue5494
440 461 mergemod.mergestate.clean(repo)
441 462 else:
442 463 # Skip commit if we are collapsing
443 464 repo.setparents(repo[p1].node())
444 465 newnode = None
445 466 # Update the state
446 467 if newnode is not None:
447 468 self.state[rev] = repo[newnode].rev()
448 469 ui.debug('rebased as %s\n' % short(newnode))
449 470 else:
450 471 if not self.collapsef:
451 472 ui.warn(_('note: rebase of %d:%s created no changes '
452 473 'to commit\n') % (rev, ctx))
453 474 self.skipped.add(rev)
454 475 self.state[rev] = p1
455 476 ui.debug('next revision set to %s\n' % p1)
456 477 else:
457 478 ui.status(_('already rebased %s as %s\n') %
458 479 (desc, repo[self.state[rev]]))
459 480 return pos
460 481
461 482 def _finishrebase(self):
462 483 repo, ui, opts = self.repo, self.ui, self.opts
463 484 if self.collapsef and not self.keepopen:
464 485 p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
465 self.state)
486 self.state, self.skipped,
487 self.obsoletenotrebased)
466 488 editopt = opts.get('edit')
467 489 editform = 'rebase.collapse'
468 490 if self.collapsemsg:
469 491 commitmsg = self.collapsemsg
470 492 else:
471 493 commitmsg = 'Collapsed revision'
472 494 for rebased in sorted(self.state):
473 495 if rebased not in self.skipped:
474 496 commitmsg += '\n* %s' % repo[rebased].description()
475 497 editopt = True
476 498 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
477 499 revtoreuse = max(self.state)
478 500
479 501 dsguard = None
480 502 if ui.configbool('rebase', 'singletransaction'):
481 503 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
482 504 with util.acceptintervention(dsguard):
483 505 newnode = concludenode(repo, revtoreuse, p1, self.external,
484 506 commitmsg=commitmsg,
485 507 extrafn=_makeextrafn(self.extrafns),
486 508 editor=editor,
487 509 keepbranches=self.keepbranchesf,
488 510 date=self.date)
489 511 if newnode is not None:
490 512 newrev = repo[newnode].rev()
491 513 for oldrev in self.state.iterkeys():
492 514 self.state[oldrev] = newrev
493 515
494 516 if 'qtip' in repo.tags():
495 517 updatemq(repo, self.state, self.skipped, **opts)
496 518
497 519 # restore original working directory
498 520 # (we do this before stripping)
499 521 newwd = self.state.get(self.originalwd, self.originalwd)
500 522 if newwd < 0:
501 523 # original directory is a parent of rebase set root or ignored
502 524 newwd = self.originalwd
503 525 if newwd not in [c.rev() for c in repo[None].parents()]:
504 526 ui.note(_("update back to initial working directory parent\n"))
505 527 hg.updaterepo(repo, newwd, False)
506 528
507 529 if not self.keepf:
508 530 collapsedas = None
509 531 if self.collapsef:
510 532 collapsedas = newnode
511 533 clearrebased(ui, repo, self.destmap, self.state, self.skipped,
512 534 collapsedas)
513 535
514 536 clearstatus(repo)
515 537 clearcollapsemsg(repo)
516 538
517 539 ui.note(_("rebase completed\n"))
518 540 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
519 541 if self.skipped:
520 542 skippedlen = len(self.skipped)
521 543 ui.note(_("%d revisions have been skipped\n") % skippedlen)
522 544
523 545 if (self.activebookmark and self.activebookmark in repo._bookmarks and
524 546 repo['.'].node() == repo._bookmarks[self.activebookmark]):
525 547 bookmarks.activate(repo, self.activebookmark)
526 548
527 549 @command('rebase',
528 550 [('s', 'source', '',
529 551 _('rebase the specified changeset and descendants'), _('REV')),
530 552 ('b', 'base', '',
531 553 _('rebase everything from branching point of specified changeset'),
532 554 _('REV')),
533 555 ('r', 'rev', [],
534 556 _('rebase these revisions'),
535 557 _('REV')),
536 558 ('d', 'dest', '',
537 559 _('rebase onto the specified changeset'), _('REV')),
538 560 ('', 'collapse', False, _('collapse the rebased changesets')),
539 561 ('m', 'message', '',
540 562 _('use text as collapse commit message'), _('TEXT')),
541 563 ('e', 'edit', False, _('invoke editor on commit messages')),
542 564 ('l', 'logfile', '',
543 565 _('read collapse commit message from file'), _('FILE')),
544 566 ('k', 'keep', False, _('keep original changesets')),
545 567 ('', 'keepbranches', False, _('keep original branch names')),
546 568 ('D', 'detach', False, _('(DEPRECATED)')),
547 569 ('i', 'interactive', False, _('(DEPRECATED)')),
548 570 ('t', 'tool', '', _('specify merge tool')),
549 571 ('c', 'continue', False, _('continue an interrupted rebase')),
550 572 ('a', 'abort', False, _('abort an interrupted rebase'))] +
551 573 templateopts,
552 574 _('[-s REV | -b REV] [-d REV] [OPTION]'))
553 575 def rebase(ui, repo, **opts):
554 576 """move changeset (and descendants) to a different branch
555 577
556 578 Rebase uses repeated merging to graft changesets from one part of
557 579 history (the source) onto another (the destination). This can be
558 580 useful for linearizing *local* changes relative to a master
559 581 development tree.
560 582
561 583 Published commits cannot be rebased (see :hg:`help phases`).
562 584 To copy commits, see :hg:`help graft`.
563 585
564 586 If you don't specify a destination changeset (``-d/--dest``), rebase
565 587 will use the same logic as :hg:`merge` to pick a destination. if
566 588 the current branch contains exactly one other head, the other head
567 589 is merged with by default. Otherwise, an explicit revision with
568 590 which to merge with must be provided. (destination changeset is not
569 591 modified by rebasing, but new changesets are added as its
570 592 descendants.)
571 593
572 594 Here are the ways to select changesets:
573 595
574 596 1. Explicitly select them using ``--rev``.
575 597
576 598 2. Use ``--source`` to select a root changeset and include all of its
577 599 descendants.
578 600
579 601 3. Use ``--base`` to select a changeset; rebase will find ancestors
580 602 and their descendants which are not also ancestors of the destination.
581 603
582 604 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
583 605 rebase will use ``--base .`` as above.
584 606
585 607 Rebase will destroy original changesets unless you use ``--keep``.
586 608 It will also move your bookmarks (even if you do).
587 609
588 610 Some changesets may be dropped if they do not contribute changes
589 611 (e.g. merges from the destination branch).
590 612
591 613 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
592 614 a named branch with two heads. You will need to explicitly specify source
593 615 and/or destination.
594 616
595 617 If you need to use a tool to automate merge/conflict decisions, you
596 618 can specify one with ``--tool``, see :hg:`help merge-tools`.
597 619 As a caveat: the tool will not be used to mediate when a file was
598 620 deleted, there is no hook presently available for this.
599 621
600 622 If a rebase is interrupted to manually resolve a conflict, it can be
601 623 continued with --continue/-c or aborted with --abort/-a.
602 624
603 625 .. container:: verbose
604 626
605 627 Examples:
606 628
607 629 - move "local changes" (current commit back to branching point)
608 630 to the current branch tip after a pull::
609 631
610 632 hg rebase
611 633
612 634 - move a single changeset to the stable branch::
613 635
614 636 hg rebase -r 5f493448 -d stable
615 637
616 638 - splice a commit and all its descendants onto another part of history::
617 639
618 640 hg rebase --source c0c3 --dest 4cf9
619 641
620 642 - rebase everything on a branch marked by a bookmark onto the
621 643 default branch::
622 644
623 645 hg rebase --base myfeature --dest default
624 646
625 647 - collapse a sequence of changes into a single commit::
626 648
627 649 hg rebase --collapse -r 1520:1525 -d .
628 650
629 651 - move a named branch while preserving its name::
630 652
631 653 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
632 654
633 655 Configuration Options:
634 656
635 657 You can make rebase require a destination if you set the following config
636 658 option::
637 659
638 660 [commands]
639 661 rebase.requiredest = True
640 662
641 663 By default, rebase will close the transaction after each commit. For
642 664 performance purposes, you can configure rebase to use a single transaction
643 665 across the entire rebase. WARNING: This setting introduces a significant
644 666 risk of losing the work you've done in a rebase if the rebase aborts
645 667 unexpectedly::
646 668
647 669 [rebase]
648 670 singletransaction = True
649 671
650 672 Return Values:
651 673
652 674 Returns 0 on success, 1 if nothing to rebase or there are
653 675 unresolved conflicts.
654 676
655 677 """
656 678 rbsrt = rebaseruntime(repo, ui, opts)
657 679
658 680 with repo.wlock(), repo.lock():
659 681 # Validate input and define rebasing points
660 682 destf = opts.get('dest', None)
661 683 srcf = opts.get('source', None)
662 684 basef = opts.get('base', None)
663 685 revf = opts.get('rev', [])
664 686 # search default destination in this space
665 687 # used in the 'hg pull --rebase' case, see issue 5214.
666 688 destspace = opts.get('_destspace')
667 689 contf = opts.get('continue')
668 690 abortf = opts.get('abort')
669 691 if opts.get('interactive'):
670 692 try:
671 693 if extensions.find('histedit'):
672 694 enablehistedit = ''
673 695 except KeyError:
674 696 enablehistedit = " --config extensions.histedit="
675 697 help = "hg%s help -e histedit" % enablehistedit
676 698 msg = _("interactive history editing is supported by the "
677 699 "'histedit' extension (see \"%s\")") % help
678 700 raise error.Abort(msg)
679 701
680 702 if rbsrt.collapsemsg and not rbsrt.collapsef:
681 703 raise error.Abort(
682 704 _('message can only be specified with collapse'))
683 705
684 706 if contf or abortf:
685 707 if contf and abortf:
686 708 raise error.Abort(_('cannot use both abort and continue'))
687 709 if rbsrt.collapsef:
688 710 raise error.Abort(
689 711 _('cannot use collapse with continue or abort'))
690 712 if srcf or basef or destf:
691 713 raise error.Abort(
692 714 _('abort and continue do not allow specifying revisions'))
693 715 if abortf and opts.get('tool', False):
694 716 ui.warn(_('tool option will be ignored\n'))
695 717 if contf:
696 718 ms = mergemod.mergestate.read(repo)
697 719 mergeutil.checkunresolved(ms)
698 720
699 721 retcode = rbsrt._prepareabortorcontinue(abortf)
700 722 if retcode is not None:
701 723 return retcode
702 724 else:
703 725 destmap = _definedestmap(ui, repo, destf, srcf, basef, revf,
704 726 destspace=destspace)
705 727 retcode = rbsrt._preparenewrebase(destmap)
706 728 if retcode is not None:
707 729 return retcode
708 730
709 731 tr = None
710 732 dsguard = None
711 733
712 734 singletr = ui.configbool('rebase', 'singletransaction')
713 735 if singletr:
714 736 tr = repo.transaction('rebase')
715 737 with util.acceptintervention(tr):
716 738 if singletr:
717 739 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
718 740 with util.acceptintervention(dsguard):
719 741 rbsrt._performrebase(tr)
720 742
721 743 rbsrt._finishrebase()
722 744
723 745 def _definedestmap(ui, repo, destf=None, srcf=None, basef=None, revf=None,
724 746 destspace=None):
725 747 """use revisions argument to define destmap {srcrev: destrev}"""
726 748 if revf is None:
727 749 revf = []
728 750
729 751 # destspace is here to work around issues with `hg pull --rebase` see
730 752 # issue5214 for details
731 753 if srcf and basef:
732 754 raise error.Abort(_('cannot specify both a source and a base'))
733 755 if revf and basef:
734 756 raise error.Abort(_('cannot specify both a revision and a base'))
735 757 if revf and srcf:
736 758 raise error.Abort(_('cannot specify both a revision and a source'))
737 759
738 760 cmdutil.checkunfinished(repo)
739 761 cmdutil.bailifchanged(repo)
740 762
741 763 if ui.configbool('commands', 'rebase.requiredest') and not destf:
742 764 raise error.Abort(_('you must specify a destination'),
743 765 hint=_('use: hg rebase -d REV'))
744 766
745 767 dest = None
746 768
747 769 if revf:
748 770 rebaseset = scmutil.revrange(repo, revf)
749 771 if not rebaseset:
750 772 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
751 773 return None
752 774 elif srcf:
753 775 src = scmutil.revrange(repo, [srcf])
754 776 if not src:
755 777 ui.status(_('empty "source" revision set - nothing to rebase\n'))
756 778 return None
757 779 rebaseset = repo.revs('(%ld)::', src)
758 780 assert rebaseset
759 781 else:
760 782 base = scmutil.revrange(repo, [basef or '.'])
761 783 if not base:
762 784 ui.status(_('empty "base" revision set - '
763 785 "can't compute rebase set\n"))
764 786 return None
765 787 if destf:
766 788 # --base does not support multiple destinations
767 789 dest = scmutil.revsingle(repo, destf)
768 790 else:
769 791 dest = repo[_destrebase(repo, base, destspace=destspace)]
770 792 destf = str(dest)
771 793
772 794 roots = [] # selected children of branching points
773 795 bpbase = {} # {branchingpoint: [origbase]}
774 796 for b in base: # group bases by branching points
775 797 bp = repo.revs('ancestor(%d, %d)', b, dest).first()
776 798 bpbase[bp] = bpbase.get(bp, []) + [b]
777 799 if None in bpbase:
778 800 # emulate the old behavior, showing "nothing to rebase" (a better
779 801 # behavior may be abort with "cannot find branching point" error)
780 802 bpbase.clear()
781 803 for bp, bs in bpbase.iteritems(): # calculate roots
782 804 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
783 805
784 806 rebaseset = repo.revs('%ld::', roots)
785 807
786 808 if not rebaseset:
787 809 # transform to list because smartsets are not comparable to
788 810 # lists. This should be improved to honor laziness of
789 811 # smartset.
790 812 if list(base) == [dest.rev()]:
791 813 if basef:
792 814 ui.status(_('nothing to rebase - %s is both "base"'
793 815 ' and destination\n') % dest)
794 816 else:
795 817 ui.status(_('nothing to rebase - working directory '
796 818 'parent is also destination\n'))
797 819 elif not repo.revs('%ld - ::%d', base, dest):
798 820 if basef:
799 821 ui.status(_('nothing to rebase - "base" %s is '
800 822 'already an ancestor of destination '
801 823 '%s\n') %
802 824 ('+'.join(str(repo[r]) for r in base),
803 825 dest))
804 826 else:
805 827 ui.status(_('nothing to rebase - working '
806 828 'directory parent is already an '
807 829 'ancestor of destination %s\n') % dest)
808 830 else: # can it happen?
809 831 ui.status(_('nothing to rebase from %s to %s\n') %
810 832 ('+'.join(str(repo[r]) for r in base), dest))
811 833 return None
812 834
813 835 if not destf:
814 836 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
815 837 destf = str(dest)
816 838
817 839 allsrc = revsetlang.formatspec('%ld', rebaseset)
818 840 alias = {'ALLSRC': allsrc}
819 841
820 842 if dest is None:
821 843 try:
822 844 # fast path: try to resolve dest without SRC alias
823 845 dest = scmutil.revsingle(repo, destf, localalias=alias)
824 846 except error.RepoLookupError:
825 847 if not ui.configbool('experimental', 'rebase.multidest'):
826 848 raise
827 849 # multi-dest path: resolve dest for each SRC separately
828 850 destmap = {}
829 851 for r in rebaseset:
830 852 alias['SRC'] = revsetlang.formatspec('%d', r)
831 853 # use repo.anyrevs instead of scmutil.revsingle because we
832 854 # don't want to abort if destset is empty.
833 855 destset = repo.anyrevs([destf], user=True, localalias=alias)
834 856 size = len(destset)
835 857 if size == 1:
836 858 destmap[r] = destset.first()
837 859 elif size == 0:
838 860 ui.note(_('skipping %s - empty destination\n') % repo[r])
839 861 else:
840 862 raise error.Abort(_('rebase destination for %s is not '
841 863 'unique') % repo[r])
842 864
843 865 if dest is not None:
844 866 # single-dest case: assign dest to each rev in rebaseset
845 867 destrev = dest.rev()
846 868 destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
847 869
848 870 if not destmap:
849 871 ui.status(_('nothing to rebase - empty destination\n'))
850 872 return None
851 873
852 874 return destmap
853 875
854 876 def externalparent(repo, state, destancestors):
855 877 """Return the revision that should be used as the second parent
856 878 when the revisions in state is collapsed on top of destancestors.
857 879 Abort if there is more than one parent.
858 880 """
859 881 parents = set()
860 882 source = min(state)
861 883 for rev in state:
862 884 if rev == source:
863 885 continue
864 886 for p in repo[rev].parents():
865 887 if (p.rev() not in state
866 888 and p.rev() not in destancestors):
867 889 parents.add(p.rev())
868 890 if not parents:
869 891 return nullrev
870 892 if len(parents) == 1:
871 893 return parents.pop()
872 894 raise error.Abort(_('unable to collapse on top of %s, there is more '
873 895 'than one external parent: %s') %
874 896 (max(destancestors),
875 897 ', '.join(str(p) for p in sorted(parents))))
876 898
877 899 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
878 900 keepbranches=False, date=None):
879 901 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
880 902 but also store useful information in extra.
881 903 Return node of committed revision.'''
882 904 dsguard = util.nullcontextmanager()
883 905 if not repo.ui.configbool('rebase', 'singletransaction'):
884 906 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
885 907 with dsguard:
886 908 repo.setparents(repo[p1].node(), repo[p2].node())
887 909 ctx = repo[rev]
888 910 if commitmsg is None:
889 911 commitmsg = ctx.description()
890 912 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
891 913 extra = {'rebase_source': ctx.hex()}
892 914 if extrafn:
893 915 extrafn(ctx, extra)
894 916
895 917 destphase = max(ctx.phase(), phases.draft)
896 918 overrides = {('phases', 'new-commit'): destphase}
897 919 with repo.ui.configoverride(overrides, 'rebase'):
898 920 if keepbranch:
899 921 repo.ui.setconfig('ui', 'allowemptycommit', True)
900 922 # Commit might fail if unresolved files exist
901 923 if date is None:
902 924 date = ctx.date()
903 925 newnode = repo.commit(text=commitmsg, user=ctx.user(),
904 926 date=date, extra=extra, editor=editor)
905 927
906 928 repo.dirstate.setbranch(repo[newnode].branch())
907 929 return newnode
908 930
909 931 def rebasenode(repo, rev, p1, base, state, collapse, dest):
910 932 'Rebase a single revision rev on top of p1 using base as merge ancestor'
911 933 # Merge phase
912 934 # Update to destination and merge it with local
913 935 if repo['.'].rev() != p1:
914 936 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
915 937 mergemod.update(repo, p1, False, True)
916 938 else:
917 939 repo.ui.debug(" already in destination\n")
918 940 repo.dirstate.write(repo.currenttransaction())
919 941 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
920 942 if base is not None:
921 943 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
922 944 # When collapsing in-place, the parent is the common ancestor, we
923 945 # have to allow merging with it.
924 946 stats = mergemod.update(repo, rev, True, True, base, collapse,
925 947 labels=['dest', 'source'])
926 948 if collapse:
927 949 copies.duplicatecopies(repo, rev, dest)
928 950 else:
929 951 # If we're not using --collapse, we need to
930 952 # duplicate copies between the revision we're
931 953 # rebasing and its first parent, but *not*
932 954 # duplicate any copies that have already been
933 955 # performed in the destination.
934 956 p1rev = repo[rev].p1().rev()
935 957 copies.duplicatecopies(repo, rev, p1rev, skiprev=dest)
936 958 return stats
937 959
938 def adjustdest(repo, rev, destmap, state):
960 def adjustdest(repo, rev, destmap, state, skipped):
939 961 """adjust rebase destination given the current rebase state
940 962
941 963 rev is what is being rebased. Return a list of two revs, which are the
942 964 adjusted destinations for rev's p1 and p2, respectively. If a parent is
943 965 nullrev, return dest without adjustment for it.
944 966
945 967 For example, when doing rebasing B+E to F, C to G, rebase will first move B
946 968 to B1, and E's destination will be adjusted from F to B1.
947 969
948 970 B1 <- written during rebasing B
949 971 |
950 972 F <- original destination of B, E
951 973 |
952 974 | E <- rev, which is being rebased
953 975 | |
954 976 | D <- prev, one parent of rev being checked
955 977 | |
956 978 | x <- skipped, ex. no successor or successor in (::dest)
957 979 | |
958 980 | C <- rebased as C', different destination
959 981 | |
960 982 | B <- rebased as B1 C'
961 983 |/ |
962 984 A G <- destination of C, different
963 985
964 986 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
965 987 first move C to C1, G to G1, and when it's checking H, the adjusted
966 988 destinations will be [C1, G1].
967 989
968 990 H C1 G1
969 991 /| | /
970 992 F G |/
971 993 K | | -> K
972 994 | C D |
973 995 | |/ |
974 996 | B | ...
975 997 |/ |/
976 998 A A
977 999
978 1000 Besides, adjust dest according to existing rebase information. For example,
979 1001
980 1002 B C D B needs to be rebased on top of C, C needs to be rebased on top
981 1003 \|/ of D. We will rebase C first.
982 1004 A
983 1005
984 1006 C' After rebasing C, when considering B's destination, use C'
985 1007 | instead of the original C.
986 1008 B D
987 1009 \ /
988 1010 A
989 1011 """
990 1012 # pick already rebased revs with same dest from state as interesting source
991 1013 dest = destmap[rev]
992 source = [s for s, d in state.items() if d > 0 and destmap[s] == dest]
1014 source = [s for s, d in state.items()
1015 if d > 0 and destmap[s] == dest and s not in skipped]
993 1016
994 1017 result = []
995 1018 for prev in repo.changelog.parentrevs(rev):
996 1019 adjusted = dest
997 1020 if prev != nullrev:
998 1021 candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
999 1022 if candidate is not None:
1000 1023 adjusted = state[candidate]
1001 1024 if adjusted == dest and dest in state:
1002 1025 adjusted = state[dest]
1003 1026 if adjusted == revtodo:
1004 1027 # sortsource should produce an order that makes this impossible
1005 1028 raise error.ProgrammingError(
1006 1029 'rev %d should be rebased already at this time' % dest)
1007 1030 result.append(adjusted)
1008 1031 return result
1009 1032
1010 1033 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
1011 1034 """
1012 1035 Abort if rebase will create divergence or rebase is noop because of markers
1013 1036
1014 1037 `rebaseobsrevs`: set of obsolete revision in source
1015 1038 `rebaseobsskipped`: set of revisions from source skipped because they have
1016 1039 successors in destination
1017 1040 """
1018 1041 # Obsolete node with successors not in dest leads to divergence
1019 1042 divergenceok = ui.configbool('experimental',
1020 1043 'allowdivergence')
1021 1044 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
1022 1045
1023 1046 if divergencebasecandidates and not divergenceok:
1024 1047 divhashes = (str(repo[r])
1025 1048 for r in divergencebasecandidates)
1026 1049 msg = _("this rebase will cause "
1027 1050 "divergences from: %s")
1028 1051 h = _("to force the rebase please set "
1029 1052 "experimental.allowdivergence=True")
1030 1053 raise error.Abort(msg % (",".join(divhashes),), hint=h)
1031 1054
1032 1055 def successorrevs(repo, rev):
1033 1056 """yield revision numbers for successors of rev"""
1034 1057 unfi = repo.unfiltered()
1035 1058 nodemap = unfi.changelog.nodemap
1036 1059 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
1037 1060 if s in nodemap:
1038 1061 yield nodemap[s]
1039 1062
1040 def defineparents(repo, rev, destmap, state):
1063 def defineparents(repo, rev, destmap, state, skipped, obsskipped):
1041 1064 """Return new parents and optionally a merge base for rev being rebased
1042 1065
1043 1066 The destination specified by "dest" cannot always be used directly because
1044 1067 previously rebase result could affect destination. For example,
1045 1068
1046 1069 D E rebase -r C+D+E -d B
1047 1070 |/ C will be rebased to C'
1048 1071 B C D's new destination will be C' instead of B
1049 1072 |/ E's new destination will be C' instead of B
1050 1073 A
1051 1074
1052 1075 The new parents of a merge is slightly more complicated. See the comment
1053 1076 block below.
1054 1077 """
1055 1078 cl = repo.changelog
1056 1079 def isancestor(a, b):
1057 1080 # take revision numbers instead of nodes
1058 1081 if a == b:
1059 1082 return True
1060 1083 elif a > b:
1061 1084 return False
1062 1085 return cl.isancestor(cl.node(a), cl.node(b))
1063 1086
1064 1087 dest = destmap[rev]
1065 1088 oldps = repo.changelog.parentrevs(rev) # old parents
1066 1089 newps = [nullrev, nullrev] # new parents
1067 dests = adjustdest(repo, rev, destmap, state) # adjusted destinations
1090 dests = adjustdest(repo, rev, destmap, state, skipped)
1068 1091 bases = list(oldps) # merge base candidates, initially just old parents
1069 1092
1070 1093 if all(r == nullrev for r in oldps[1:]):
1071 1094 # For non-merge changeset, just move p to adjusted dest as requested.
1072 1095 newps[0] = dests[0]
1073 1096 else:
1074 1097 # For merge changeset, if we move p to dests[i] unconditionally, both
1075 1098 # parents may change and the end result looks like "the merge loses a
1076 1099 # parent", which is a surprise. This is a limit because "--dest" only
1077 1100 # accepts one dest per src.
1078 1101 #
1079 1102 # Therefore, only move p with reasonable conditions (in this order):
1080 1103 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1081 1104 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1082 1105 #
1083 1106 # Comparing with adjustdest, the logic here does some additional work:
1084 1107 # 1. decide which parents will not be moved towards dest
1085 1108 # 2. if the above decision is "no", should a parent still be moved
1086 1109 # because it was rebased?
1087 1110 #
1088 1111 # For example:
1089 1112 #
1090 1113 # C # "rebase -r C -d D" is an error since none of the parents
1091 1114 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1092 1115 # A B D # B (using rule "2."), since B will be rebased.
1093 1116 #
1094 1117 # The loop tries to be not rely on the fact that a Mercurial node has
1095 1118 # at most 2 parents.
1096 1119 for i, p in enumerate(oldps):
1097 1120 np = p # new parent
1098 1121 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1099 1122 np = dests[i]
1100 1123 elif p in state and state[p] > 0:
1101 1124 np = state[p]
1102 1125
1103 1126 # "bases" only record "special" merge bases that cannot be
1104 1127 # calculated from changelog DAG (i.e. isancestor(p, np) is False).
1105 1128 # For example:
1106 1129 #
1107 1130 # B' # rebase -s B -d D, when B was rebased to B'. dest for C
1108 1131 # | C # is B', but merge base for C is B, instead of
1109 1132 # D | # changelog.ancestor(C, B') == A. If changelog DAG and
1110 1133 # | B # "state" edges are merged (so there will be an edge from
1111 1134 # |/ # B to B'), the merge base is still ancestor(C, B') in
1112 1135 # A # the merged graph.
1113 1136 #
1114 1137 # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
1115 1138 # which uses "virtual null merge" to explain this situation.
1116 1139 if isancestor(p, np):
1117 1140 bases[i] = nullrev
1118 1141
1119 1142 # If one parent becomes an ancestor of the other, drop the ancestor
1120 1143 for j, x in enumerate(newps[:i]):
1121 1144 if x == nullrev:
1122 1145 continue
1123 1146 if isancestor(np, x): # CASE-1
1124 1147 np = nullrev
1125 1148 elif isancestor(x, np): # CASE-2
1126 1149 newps[j] = np
1127 1150 np = nullrev
1128 1151 # New parents forming an ancestor relationship does not
1129 1152 # mean the old parents have a similar relationship. Do not
1130 1153 # set bases[x] to nullrev.
1131 1154 bases[j], bases[i] = bases[i], bases[j]
1132 1155
1133 1156 newps[i] = np
1134 1157
1135 1158 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1136 1159 # base. If only p2 changes, merging using unchanged p1 as merge base is
1137 1160 # suboptimal. Therefore swap parents to make the merge sane.
1138 1161 if newps[1] != nullrev and oldps[0] == newps[0]:
1139 1162 assert len(newps) == 2 and len(oldps) == 2
1140 1163 newps.reverse()
1141 1164 bases.reverse()
1142 1165
1143 1166 # No parent change might be an error because we fail to make rev a
1144 1167 # descendent of requested dest. This can happen, for example:
1145 1168 #
1146 1169 # C # rebase -r C -d D
1147 1170 # /| # None of A and B will be changed to D and rebase fails.
1148 1171 # A B D
1149 1172 if set(newps) == set(oldps) and dest not in newps:
1150 1173 raise error.Abort(_('cannot rebase %d:%s without '
1151 1174 'moving at least one of its parents')
1152 1175 % (rev, repo[rev]))
1153 1176
1154 1177 # Source should not be ancestor of dest. The check here guarantees it's
1155 1178 # impossible. With multi-dest, the initial check does not cover complex
1156 1179 # cases since we don't have abstractions to dry-run rebase cheaply.
1157 1180 if any(p != nullrev and isancestor(rev, p) for p in newps):
1158 1181 raise error.Abort(_('source is ancestor of destination'))
1159 1182
1160 1183 # "rebasenode" updates to new p1, use the corresponding merge base.
1161 1184 if bases[0] != nullrev:
1162 1185 base = bases[0]
1163 1186 else:
1164 1187 base = None
1165 1188
1166 1189 # Check if the merge will contain unwanted changes. That may happen if
1167 1190 # there are multiple special (non-changelog ancestor) merge bases, which
1168 1191 # cannot be handled well by the 3-way merge algorithm. For example:
1169 1192 #
1170 1193 # F
1171 1194 # /|
1172 1195 # D E # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
1173 1196 # | | # as merge base, the difference between D and F will include
1174 1197 # B C # C, so the rebased F will contain C surprisingly. If "E" was
1175 1198 # |/ # chosen, the rebased F will contain B.
1176 1199 # A Z
1177 1200 #
1178 1201 # But our merge base candidates (D and E in above case) could still be
1179 1202 # better than the default (ancestor(F, Z) == null). Therefore still
1180 1203 # pick one (so choose p1 above).
1181 1204 if sum(1 for b in bases if b != nullrev) > 1:
1182 1205 unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
1183 1206 for i, base in enumerate(bases):
1184 1207 if base == nullrev:
1185 1208 continue
1186 1209 # Revisions in the side (not chosen as merge base) branch that
1187 1210 # might contain "surprising" contents
1188 1211 siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
1189 1212 bases, base, base, dest))
1190 1213
1191 1214 # If those revisions are covered by rebaseset, the result is good.
1192 1215 # A merge in rebaseset would be considered to cover its ancestors.
1193 1216 if siderevs:
1194 rebaseset = [r for r, d in state.items() if d > 0]
1217 rebaseset = [r for r, d in state.items()
1218 if d > 0 and r not in obsskipped]
1195 1219 merges = [r for r in rebaseset
1196 1220 if cl.parentrevs(r)[1] != nullrev]
1197 1221 unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
1198 1222 siderevs, merges, rebaseset))
1199 1223
1200 1224 # Choose a merge base that has a minimal number of unwanted revs.
1201 1225 l, i = min((len(revs), i)
1202 1226 for i, revs in enumerate(unwanted) if revs is not None)
1203 1227 base = bases[i]
1204 1228
1205 1229 # newps[0] should match merge base if possible. Currently, if newps[i]
1206 1230 # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
1207 1231 # the other's ancestor. In that case, it's fine to not swap newps here.
1208 1232 # (see CASE-1 and CASE-2 above)
1209 1233 if i != 0 and newps[i] != nullrev:
1210 1234 newps[0], newps[i] = newps[i], newps[0]
1211 1235
1212 1236 # The merge will include unwanted revisions. Abort now. Revisit this if
1213 1237 # we have a more advanced merge algorithm that handles multiple bases.
1214 1238 if l > 0:
1215 1239 unwanteddesc = _(' or ').join(
1216 1240 (', '.join('%d:%s' % (r, repo[r]) for r in revs)
1217 1241 for revs in unwanted if revs is not None))
1218 1242 raise error.Abort(
1219 1243 _('rebasing %d:%s will include unwanted changes from %s')
1220 1244 % (rev, repo[rev], unwanteddesc))
1221 1245
1222 1246 repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
1223 1247
1224 1248 return newps[0], newps[1], base
1225 1249
1226 1250 def isagitpatch(repo, patchname):
1227 1251 'Return true if the given patch is in git format'
1228 1252 mqpatch = os.path.join(repo.mq.path, patchname)
1229 1253 for line in patch.linereader(file(mqpatch, 'rb')):
1230 1254 if line.startswith('diff --git'):
1231 1255 return True
1232 1256 return False
1233 1257
1234 1258 def updatemq(repo, state, skipped, **opts):
1235 1259 'Update rebased mq patches - finalize and then import them'
1236 1260 mqrebase = {}
1237 1261 mq = repo.mq
1238 1262 original_series = mq.fullseries[:]
1239 1263 skippedpatches = set()
1240 1264
1241 1265 for p in mq.applied:
1242 1266 rev = repo[p.node].rev()
1243 1267 if rev in state:
1244 1268 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1245 1269 (rev, p.name))
1246 1270 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1247 1271 else:
1248 1272 # Applied but not rebased, not sure this should happen
1249 1273 skippedpatches.add(p.name)
1250 1274
1251 1275 if mqrebase:
1252 1276 mq.finish(repo, mqrebase.keys())
1253 1277
1254 1278 # We must start import from the newest revision
1255 1279 for rev in sorted(mqrebase, reverse=True):
1256 1280 if rev not in skipped:
1257 1281 name, isgit = mqrebase[rev]
1258 1282 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1259 1283 (name, state[rev], repo[state[rev]]))
1260 1284 mq.qimport(repo, (), patchname=name, git=isgit,
1261 1285 rev=[str(state[rev])])
1262 1286 else:
1263 1287 # Rebased and skipped
1264 1288 skippedpatches.add(mqrebase[rev][0])
1265 1289
1266 1290 # Patches were either applied and rebased and imported in
1267 1291 # order, applied and removed or unapplied. Discard the removed
1268 1292 # ones while preserving the original series order and guards.
1269 1293 newseries = [s for s in original_series
1270 1294 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1271 1295 mq.fullseries[:] = newseries
1272 1296 mq.seriesdirty = True
1273 1297 mq.savedirty()
1274 1298
1275 1299 def storecollapsemsg(repo, collapsemsg):
1276 1300 'Store the collapse message to allow recovery'
1277 1301 collapsemsg = collapsemsg or ''
1278 1302 f = repo.vfs("last-message.txt", "w")
1279 1303 f.write("%s\n" % collapsemsg)
1280 1304 f.close()
1281 1305
1282 1306 def clearcollapsemsg(repo):
1283 1307 'Remove collapse message file'
1284 1308 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1285 1309
1286 1310 def restorecollapsemsg(repo, isabort):
1287 1311 'Restore previously stored collapse message'
1288 1312 try:
1289 1313 f = repo.vfs("last-message.txt")
1290 1314 collapsemsg = f.readline().strip()
1291 1315 f.close()
1292 1316 except IOError as err:
1293 1317 if err.errno != errno.ENOENT:
1294 1318 raise
1295 1319 if isabort:
1296 1320 # Oh well, just abort like normal
1297 1321 collapsemsg = ''
1298 1322 else:
1299 1323 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1300 1324 return collapsemsg
1301 1325
1302 1326 def clearstatus(repo):
1303 1327 'Remove the status files'
1304 1328 _clearrebasesetvisibiliy(repo)
1305 1329 # Make sure the active transaction won't write the state file
1306 1330 tr = repo.currenttransaction()
1307 1331 if tr:
1308 1332 tr.removefilegenerator('rebasestate')
1309 1333 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1310 1334
1311 1335 def needupdate(repo, state):
1312 1336 '''check whether we should `update --clean` away from a merge, or if
1313 1337 somehow the working dir got forcibly updated, e.g. by older hg'''
1314 1338 parents = [p.rev() for p in repo[None].parents()]
1315 1339
1316 1340 # Are we in a merge state at all?
1317 1341 if len(parents) < 2:
1318 1342 return False
1319 1343
1320 1344 # We should be standing on the first as-of-yet unrebased commit.
1321 1345 firstunrebased = min([old for old, new in state.iteritems()
1322 1346 if new == nullrev])
1323 1347 if firstunrebased in parents:
1324 1348 return True
1325 1349
1326 1350 return False
1327 1351
1328 1352 def abort(repo, originalwd, destmap, state, activebookmark=None):
1329 1353 '''Restore the repository to its original state. Additional args:
1330 1354
1331 1355 activebookmark: the name of the bookmark that should be active after the
1332 1356 restore'''
1333 1357
1334 1358 try:
1335 1359 # If the first commits in the rebased set get skipped during the rebase,
1336 1360 # their values within the state mapping will be the dest rev id. The
1337 1361 # dstates list must must not contain the dest rev (issue4896)
1338 1362 dstates = [s for r, s in state.items() if s >= 0 and s != destmap[r]]
1339 1363 immutable = [d for d in dstates if not repo[d].mutable()]
1340 1364 cleanup = True
1341 1365 if immutable:
1342 1366 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1343 1367 % ', '.join(str(repo[r]) for r in immutable),
1344 1368 hint=_("see 'hg help phases' for details"))
1345 1369 cleanup = False
1346 1370
1347 1371 descendants = set()
1348 1372 if dstates:
1349 1373 descendants = set(repo.changelog.descendants(dstates))
1350 1374 if descendants - set(dstates):
1351 1375 repo.ui.warn(_("warning: new changesets detected on destination "
1352 1376 "branch, can't strip\n"))
1353 1377 cleanup = False
1354 1378
1355 1379 if cleanup:
1356 1380 shouldupdate = False
1357 1381 rebased = [s for r, s in state.items()
1358 1382 if s >= 0 and s != destmap[r]]
1359 1383 if rebased:
1360 1384 strippoints = [
1361 1385 c.node() for c in repo.set('roots(%ld)', rebased)]
1362 1386
1363 1387 updateifonnodes = set(rebased)
1364 1388 updateifonnodes.update(destmap.values())
1365 1389 updateifonnodes.add(originalwd)
1366 1390 shouldupdate = repo['.'].rev() in updateifonnodes
1367 1391
1368 1392 # Update away from the rebase if necessary
1369 1393 if shouldupdate or needupdate(repo, state):
1370 1394 mergemod.update(repo, originalwd, False, True)
1371 1395
1372 1396 # Strip from the first rebased revision
1373 1397 if rebased:
1374 1398 # no backup of rebased cset versions needed
1375 1399 repair.strip(repo.ui, repo, strippoints)
1376 1400
1377 1401 if activebookmark and activebookmark in repo._bookmarks:
1378 1402 bookmarks.activate(repo, activebookmark)
1379 1403
1380 1404 finally:
1381 1405 clearstatus(repo)
1382 1406 clearcollapsemsg(repo)
1383 1407 repo.ui.warn(_('rebase aborted\n'))
1384 1408 return 0
1385 1409
1386 1410 def sortsource(destmap):
1387 1411 """yield source revisions in an order that we only rebase things once
1388 1412
1389 1413 If source and destination overlaps, we should filter out revisions
1390 1414 depending on other revisions which hasn't been rebased yet.
1391 1415
1392 1416 Yield a sorted list of revisions each time.
1393 1417
1394 1418 For example, when rebasing A to B, B to C. This function yields [B], then
1395 1419 [A], indicating B needs to be rebased first.
1396 1420
1397 1421 Raise if there is a cycle so the rebase is impossible.
1398 1422 """
1399 1423 srcset = set(destmap)
1400 1424 while srcset:
1401 1425 srclist = sorted(srcset)
1402 1426 result = []
1403 1427 for r in srclist:
1404 1428 if destmap[r] not in srcset:
1405 1429 result.append(r)
1406 1430 if not result:
1407 1431 raise error.Abort(_('source and destination form a cycle'))
1408 1432 srcset -= set(result)
1409 1433 yield result
1410 1434
1411 def buildstate(repo, destmap, collapse, obsoletenotrebased):
1435 def buildstate(repo, destmap, collapse):
1412 1436 '''Define which revisions are going to be rebased and where
1413 1437
1414 1438 repo: repo
1415 1439 destmap: {srcrev: destrev}
1416 1440 '''
1417 1441 rebaseset = destmap.keys()
1418 1442 originalwd = repo['.'].rev()
1419 1443 _setrebasesetvisibility(repo, set(rebaseset) | {originalwd})
1420 1444
1421 1445 # This check isn't strictly necessary, since mq detects commits over an
1422 1446 # applied patch. But it prevents messing up the working directory when
1423 1447 # a partially completed rebase is blocked by mq.
1424 1448 if 'qtip' in repo.tags():
1425 1449 mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
1426 1450 if set(destmap.values()) & mqapplied:
1427 1451 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1428 1452
1429 1453 # Get "cycle" error early by exhausting the generator.
1430 1454 sortedsrc = list(sortsource(destmap)) # a list of sorted revs
1431 1455 if not sortedsrc:
1432 1456 raise error.Abort(_('no matching revisions'))
1433 1457
1434 1458 # Only check the first batch of revisions to rebase not depending on other
1435 1459 # rebaseset. This means "source is ancestor of destination" for the second
1436 1460 # (and following) batches of revisions are not checked here. We rely on
1437 1461 # "defineparents" to do that check.
1438 1462 roots = list(repo.set('roots(%ld)', sortedsrc[0]))
1439 1463 if not roots:
1440 1464 raise error.Abort(_('no matching revisions'))
1441 1465 roots.sort()
1442 1466 state = dict.fromkeys(rebaseset, revtodo)
1443 1467 emptyrebase = (len(sortedsrc) == 1)
1444 1468 for root in roots:
1445 1469 dest = repo[destmap[root.rev()]]
1446 1470 commonbase = root.ancestor(dest)
1447 1471 if commonbase == root:
1448 1472 raise error.Abort(_('source is ancestor of destination'))
1449 1473 if commonbase == dest:
1450 1474 wctx = repo[None]
1451 1475 if dest == wctx.p1():
1452 1476 # when rebasing to '.', it will use the current wd branch name
1453 1477 samebranch = root.branch() == wctx.branch()
1454 1478 else:
1455 1479 samebranch = root.branch() == dest.branch()
1456 1480 if not collapse and samebranch and dest in root.parents():
1457 1481 # mark the revision as done by setting its new revision
1458 1482 # equal to its old (current) revisions
1459 1483 state[root.rev()] = root.rev()
1460 1484 repo.ui.debug('source is a child of destination\n')
1461 1485 continue
1462 1486
1463 1487 emptyrebase = False
1464 1488 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1465 1489 if emptyrebase:
1466 1490 return None
1467 1491 for rev in sorted(state):
1468 1492 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1469 1493 # if all parents of this revision are done, then so is this revision
1470 1494 if parents and all((state.get(p) == p for p in parents)):
1471 1495 state[rev] = rev
1472 unfi = repo.unfiltered()
1473 for r in obsoletenotrebased:
1474 desc = _ctxdesc(unfi[r])
1475 succ = obsoletenotrebased[r]
1476 if succ is None:
1477 msg = _('note: not rebasing %s, it has no successor\n') % desc
1478 del state[r]
1479 del destmap[r]
1480 else:
1481 destctx = unfi[succ]
1482 destdesc = '%d:%s "%s"' % (destctx.rev(), destctx,
1483 destctx.description().split('\n', 1)[0])
1484 msg = (_('note: not rebasing %s, already in destination as %s\n')
1485 % (desc, destdesc))
1486 del state[r]
1487 del destmap[r]
1488 repo.ui.status(msg)
1489 1496 return originalwd, destmap, state
1490 1497
1491 1498 def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None):
1492 1499 """dispose of rebased revision at the end of the rebase
1493 1500
1494 1501 If `collapsedas` is not None, the rebase was a collapse whose result if the
1495 1502 `collapsedas` node."""
1496 1503 tonode = repo.changelog.node
1497 1504 # Move bookmark of skipped nodes to destination. This cannot be handled
1498 1505 # by scmutil.cleanupnodes since it will treat rev as removed (no successor)
1499 1506 # and move bookmark backwards.
1500 1507 bmchanges = [(name, tonode(state[rev]))
1501 1508 for rev in skipped
1502 1509 for name in repo.nodebookmarks(tonode(rev))]
1503 1510 if bmchanges:
1504 1511 with repo.transaction('rebase') as tr:
1505 1512 repo._bookmarks.applychanges(repo, tr, bmchanges)
1506 1513 mapping = {}
1507 1514 for rev, newrev in sorted(state.items()):
1508 1515 if newrev >= 0 and newrev != rev:
1509 1516 if rev in skipped:
1510 1517 succs = ()
1511 1518 elif collapsedas is not None:
1512 1519 succs = (collapsedas,)
1513 1520 else:
1514 1521 succs = (tonode(newrev),)
1515 1522 mapping[tonode(rev)] = succs
1516 1523 scmutil.cleanupnodes(repo, mapping, 'rebase')
1517 1524
1518 1525 def pullrebase(orig, ui, repo, *args, **opts):
1519 1526 'Call rebase after pull if the latter has been invoked with --rebase'
1520 1527 ret = None
1521 1528 if opts.get('rebase'):
1522 1529 if ui.configbool('commands', 'rebase.requiredest'):
1523 1530 msg = _('rebase destination required by configuration')
1524 1531 hint = _('use hg pull followed by hg rebase -d DEST')
1525 1532 raise error.Abort(msg, hint=hint)
1526 1533
1527 1534 with repo.wlock(), repo.lock():
1528 1535 if opts.get('update'):
1529 1536 del opts['update']
1530 1537 ui.debug('--update and --rebase are not compatible, ignoring '
1531 1538 'the update flag\n')
1532 1539
1533 1540 cmdutil.checkunfinished(repo)
1534 1541 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1535 1542 'please commit or shelve your changes first'))
1536 1543
1537 1544 revsprepull = len(repo)
1538 1545 origpostincoming = commands.postincoming
1539 1546 def _dummy(*args, **kwargs):
1540 1547 pass
1541 1548 commands.postincoming = _dummy
1542 1549 try:
1543 1550 ret = orig(ui, repo, *args, **opts)
1544 1551 finally:
1545 1552 commands.postincoming = origpostincoming
1546 1553 revspostpull = len(repo)
1547 1554 if revspostpull > revsprepull:
1548 1555 # --rev option from pull conflict with rebase own --rev
1549 1556 # dropping it
1550 1557 if 'rev' in opts:
1551 1558 del opts['rev']
1552 1559 # positional argument from pull conflicts with rebase's own
1553 1560 # --source.
1554 1561 if 'source' in opts:
1555 1562 del opts['source']
1556 1563 # revsprepull is the len of the repo, not revnum of tip.
1557 1564 destspace = list(repo.changelog.revs(start=revsprepull))
1558 1565 opts['_destspace'] = destspace
1559 1566 try:
1560 1567 rebase(ui, repo, **opts)
1561 1568 except error.NoMergeDestAbort:
1562 1569 # we can maybe update instead
1563 1570 rev, _a, _b = destutil.destupdate(repo)
1564 1571 if rev == repo['.'].rev():
1565 1572 ui.status(_('nothing to rebase\n'))
1566 1573 else:
1567 1574 ui.status(_('nothing to rebase - updating instead\n'))
1568 1575 # not passing argument to get the bare update behavior
1569 1576 # with warning and trumpets
1570 1577 commands.update(ui, repo)
1571 1578 else:
1572 1579 if opts.get('tool'):
1573 1580 raise error.Abort(_('--tool can only be used with --rebase'))
1574 1581 ret = orig(ui, repo, *args, **opts)
1575 1582
1576 1583 return ret
1577 1584
1578 1585 def _setrebasesetvisibility(repo, revs):
1579 1586 """store the currently rebased set on the repo object
1580 1587
1581 1588 This is used by another function to prevent rebased revision to because
1582 1589 hidden (see issue4504)"""
1583 1590 repo = repo.unfiltered()
1584 1591 repo._rebaseset = revs
1585 1592 # invalidate cache if visibility changes
1586 1593 hiddens = repo.filteredrevcache.get('visible', set())
1587 1594 if revs & hiddens:
1588 1595 repo.invalidatevolatilesets()
1589 1596
1590 1597 def _clearrebasesetvisibiliy(repo):
1591 1598 """remove rebaseset data from the repo"""
1592 1599 repo = repo.unfiltered()
1593 1600 if '_rebaseset' in vars(repo):
1594 1601 del repo._rebaseset
1595 1602
1596 1603 def _rebasedvisible(orig, repo):
1597 1604 """ensure rebased revs stay visible (see issue4504)"""
1598 1605 blockers = orig(repo)
1599 1606 blockers.update(getattr(repo, '_rebaseset', ()))
1600 1607 return blockers
1601 1608
1602 1609 def _filterobsoleterevs(repo, revs):
1603 1610 """returns a set of the obsolete revisions in revs"""
1604 1611 return set(r for r in revs if repo[r].obsolete())
1605 1612
1606 1613 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
1607 1614 """return a mapping obsolete => successor for all obsolete nodes to be
1608 1615 rebased that have a successors in the destination
1609 1616
1610 1617 obsolete => None entries in the mapping indicate nodes with no successor"""
1611 1618 obsoletenotrebased = {}
1612 1619
1613 1620 cl = repo.unfiltered().changelog
1614 1621 nodemap = cl.nodemap
1615 1622 for srcrev in rebaseobsrevs:
1616 1623 srcnode = cl.node(srcrev)
1617 1624 destnode = cl.node(destmap[srcrev])
1618 1625 # XXX: more advanced APIs are required to handle split correctly
1619 1626 successors = list(obsutil.allsuccessors(repo.obsstore, [srcnode]))
1620 1627 if len(successors) == 1:
1621 1628 # obsutil.allsuccessors includes node itself. When the list only
1622 1629 # contains one element, it means there are no successors.
1623 1630 obsoletenotrebased[srcrev] = None
1624 1631 else:
1625 1632 for succnode in successors:
1626 1633 if succnode == srcnode or succnode not in nodemap:
1627 1634 continue
1628 1635 if cl.isancestor(succnode, destnode):
1629 1636 obsoletenotrebased[srcrev] = nodemap[succnode]
1630 1637 break
1631 1638
1632 1639 return obsoletenotrebased
1633 1640
1634 1641 def summaryhook(ui, repo):
1635 1642 if not repo.vfs.exists('rebasestate'):
1636 1643 return
1637 1644 try:
1638 1645 rbsrt = rebaseruntime(repo, ui, {})
1639 1646 rbsrt.restorestatus()
1640 1647 state = rbsrt.state
1641 1648 except error.RepoLookupError:
1642 1649 # i18n: column positioning for "hg summary"
1643 1650 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1644 1651 ui.write(msg)
1645 1652 return
1646 1653 numrebased = len([i for i in state.itervalues() if i >= 0])
1647 1654 # i18n: column positioning for "hg summary"
1648 1655 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1649 1656 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1650 1657 ui.label(_('%d remaining'), 'rebase.remaining') %
1651 1658 (len(state) - numrebased)))
1652 1659
1653 1660 def uisetup(ui):
1654 1661 #Replace pull with a decorator to provide --rebase option
1655 1662 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1656 1663 entry[1].append(('', 'rebase', None,
1657 1664 _("rebase working directory to branch head")))
1658 1665 entry[1].append(('t', 'tool', '',
1659 1666 _("specify merge tool for rebase")))
1660 1667 cmdutil.summaryhooks.add('rebase', summaryhook)
1661 1668 cmdutil.unfinishedstates.append(
1662 1669 ['rebasestate', False, False, _('rebase in progress'),
1663 1670 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1664 1671 cmdutil.afterresolvedstates.append(
1665 1672 ['rebasestate', _('hg rebase --continue')])
1666 1673 # ensure rebased rev are not hidden
1667 1674 extensions.wrapfunction(repoview, 'pinnedrevs', _rebasedvisible)
@@ -1,1242 +1,1269
1 1 ==========================
2 2 Test rebase with obsolete
3 3 ==========================
4 4
5 5 Enable obsolete
6 6
7 7 $ cat >> $HGRCPATH << EOF
8 8 > [ui]
9 9 > logtemplate= {rev}:{node|short} {desc|firstline}
10 10 > [experimental]
11 11 > stabilization=createmarkers,allowunstable
12 12 > [phases]
13 13 > publish=False
14 14 > [extensions]
15 15 > rebase=
16 16 > drawdag=$TESTDIR/drawdag.py
17 17 > EOF
18 18
19 19 Setup rebase canonical repo
20 20
21 21 $ hg init base
22 22 $ cd base
23 23 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
24 24 adding changesets
25 25 adding manifests
26 26 adding file changes
27 27 added 8 changesets with 7 changes to 7 files (+2 heads)
28 28 (run 'hg heads' to see heads, 'hg merge' to merge)
29 29 $ hg up tip
30 30 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
31 31 $ hg log -G
32 32 @ 7:02de42196ebe H
33 33 |
34 34 | o 6:eea13746799a G
35 35 |/|
36 36 o | 5:24b6387c8c8c F
37 37 | |
38 38 | o 4:9520eea781bc E
39 39 |/
40 40 | o 3:32af7686d403 D
41 41 | |
42 42 | o 2:5fddd98957c8 C
43 43 | |
44 44 | o 1:42ccdea3bb16 B
45 45 |/
46 46 o 0:cd010b8cd998 A
47 47
48 48 $ cd ..
49 49
50 50 simple rebase
51 51 ---------------------------------
52 52
53 53 $ hg clone base simple
54 54 updating to branch default
55 55 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
56 56 $ cd simple
57 57 $ hg up 32af7686d403
58 58 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
59 59 $ hg rebase -d eea13746799a
60 60 rebasing 1:42ccdea3bb16 "B"
61 61 rebasing 2:5fddd98957c8 "C"
62 62 rebasing 3:32af7686d403 "D"
63 63 $ hg log -G
64 64 @ 10:8eeb3c33ad33 D
65 65 |
66 66 o 9:2327fea05063 C
67 67 |
68 68 o 8:e4e5be0395b2 B
69 69 |
70 70 | o 7:02de42196ebe H
71 71 | |
72 72 o | 6:eea13746799a G
73 73 |\|
74 74 | o 5:24b6387c8c8c F
75 75 | |
76 76 o | 4:9520eea781bc E
77 77 |/
78 78 o 0:cd010b8cd998 A
79 79
80 80 $ hg log --hidden -G
81 81 @ 10:8eeb3c33ad33 D
82 82 |
83 83 o 9:2327fea05063 C
84 84 |
85 85 o 8:e4e5be0395b2 B
86 86 |
87 87 | o 7:02de42196ebe H
88 88 | |
89 89 o | 6:eea13746799a G
90 90 |\|
91 91 | o 5:24b6387c8c8c F
92 92 | |
93 93 o | 4:9520eea781bc E
94 94 |/
95 95 | x 3:32af7686d403 D
96 96 | |
97 97 | x 2:5fddd98957c8 C
98 98 | |
99 99 | x 1:42ccdea3bb16 B
100 100 |/
101 101 o 0:cd010b8cd998 A
102 102
103 103 $ hg debugobsolete
104 104 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 e4e5be0395b2cbd471ed22a26b1b6a1a0658a794 0 (*) {'user': 'test'} (glob)
105 105 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 2327fea05063f39961b14cb69435a9898dc9a245 0 (*) {'user': 'test'} (glob)
106 106 32af7686d403cf45b5d95f2d70cebea587ac806a 8eeb3c33ad33d452c89e5dcf611c347f978fb42b 0 (*) {'user': 'test'} (glob)
107 107
108 108
109 109 $ cd ..
110 110
111 111 empty changeset
112 112 ---------------------------------
113 113
114 114 $ hg clone base empty
115 115 updating to branch default
116 116 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
117 117 $ cd empty
118 118 $ hg up eea13746799a
119 119 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
120 120
121 121 We make a copy of both the first changeset in the rebased and some other in the
122 122 set.
123 123
124 124 $ hg graft 42ccdea3bb16 32af7686d403
125 125 grafting 1:42ccdea3bb16 "B"
126 126 grafting 3:32af7686d403 "D"
127 127 $ hg rebase -s 42ccdea3bb16 -d .
128 128 rebasing 1:42ccdea3bb16 "B"
129 129 note: rebase of 1:42ccdea3bb16 created no changes to commit
130 130 rebasing 2:5fddd98957c8 "C"
131 131 rebasing 3:32af7686d403 "D"
132 132 note: rebase of 3:32af7686d403 created no changes to commit
133 133 $ hg log -G
134 134 o 10:5ae4c968c6ac C
135 135 |
136 136 @ 9:08483444fef9 D
137 137 |
138 138 o 8:8877864f1edb B
139 139 |
140 140 | o 7:02de42196ebe H
141 141 | |
142 142 o | 6:eea13746799a G
143 143 |\|
144 144 | o 5:24b6387c8c8c F
145 145 | |
146 146 o | 4:9520eea781bc E
147 147 |/
148 148 o 0:cd010b8cd998 A
149 149
150 150 $ hg log --hidden -G
151 151 o 10:5ae4c968c6ac C
152 152 |
153 153 @ 9:08483444fef9 D
154 154 |
155 155 o 8:8877864f1edb B
156 156 |
157 157 | o 7:02de42196ebe H
158 158 | |
159 159 o | 6:eea13746799a G
160 160 |\|
161 161 | o 5:24b6387c8c8c F
162 162 | |
163 163 o | 4:9520eea781bc E
164 164 |/
165 165 | x 3:32af7686d403 D
166 166 | |
167 167 | x 2:5fddd98957c8 C
168 168 | |
169 169 | x 1:42ccdea3bb16 B
170 170 |/
171 171 o 0:cd010b8cd998 A
172 172
173 173 $ hg debugobsolete
174 174 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
175 175 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
176 176 32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (*) {'user': 'test'} (glob)
177 177
178 178
179 179 More complex case where part of the rebase set were already rebased
180 180
181 181 $ hg rebase --rev 'desc(D)' --dest 'desc(H)'
182 182 rebasing 9:08483444fef9 "D"
183 183 $ hg debugobsolete
184 184 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
185 185 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
186 186 32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (*) {'user': 'test'} (glob)
187 187 08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (*) {'user': 'test'} (glob)
188 188 $ hg log -G
189 189 @ 11:4596109a6a43 D
190 190 |
191 191 | o 10:5ae4c968c6ac C
192 192 | |
193 193 | x 9:08483444fef9 D
194 194 | |
195 195 | o 8:8877864f1edb B
196 196 | |
197 197 o | 7:02de42196ebe H
198 198 | |
199 199 | o 6:eea13746799a G
200 200 |/|
201 201 o | 5:24b6387c8c8c F
202 202 | |
203 203 | o 4:9520eea781bc E
204 204 |/
205 205 o 0:cd010b8cd998 A
206 206
207 207 $ hg rebase --source 'desc(B)' --dest 'tip' --config experimental.rebaseskipobsolete=True
208 rebasing 8:8877864f1edb "B"
208 209 note: not rebasing 9:08483444fef9 "D", already in destination as 11:4596109a6a43 "D"
209 rebasing 8:8877864f1edb "B"
210 210 rebasing 10:5ae4c968c6ac "C"
211 211 $ hg debugobsolete
212 212 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
213 213 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
214 214 32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (*) {'user': 'test'} (glob)
215 215 08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (*) {'user': 'test'} (glob)
216 216 8877864f1edb05d0e07dc4ba77b67a80a7b86672 462a34d07e599b87ea08676a449373fe4e2e1347 0 (*) {'user': 'test'} (glob)
217 217 5ae4c968c6aca831df823664e706c9d4aa34473d 98f6af4ee9539e14da4465128f894c274900b6e5 0 (*) {'user': 'test'} (glob)
218 218 $ hg log --rev 'contentdivergent()'
219 219 $ hg log -G
220 220 o 13:98f6af4ee953 C
221 221 |
222 222 o 12:462a34d07e59 B
223 223 |
224 224 @ 11:4596109a6a43 D
225 225 |
226 226 o 7:02de42196ebe H
227 227 |
228 228 | o 6:eea13746799a G
229 229 |/|
230 230 o | 5:24b6387c8c8c F
231 231 | |
232 232 | o 4:9520eea781bc E
233 233 |/
234 234 o 0:cd010b8cd998 A
235 235
236 236 $ hg log --style default --debug -r 4596109a6a4328c398bde3a4a3b6737cfade3003
237 237 changeset: 11:4596109a6a4328c398bde3a4a3b6737cfade3003
238 238 phase: draft
239 239 parent: 7:02de42196ebee42ef284b6780a87cdc96e8eaab6
240 240 parent: -1:0000000000000000000000000000000000000000
241 241 manifest: 11:a91006e3a02f1edf631f7018e6e5684cf27dd905
242 242 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
243 243 date: Sat Apr 30 15:24:48 2011 +0200
244 244 files+: D
245 245 extra: branch=default
246 246 extra: rebase_source=08483444fef91d6224f6655ee586a65d263ad34c
247 247 extra: source=32af7686d403cf45b5d95f2d70cebea587ac806a
248 248 description:
249 249 D
250 250
251 251
252 252 $ hg up -qr 'desc(G)'
253 253 $ hg graft 4596109a6a4328c398bde3a4a3b6737cfade3003
254 254 grafting 11:4596109a6a43 "D"
255 255 $ hg up -qr 'desc(E)'
256 256 $ hg rebase -s tip -d .
257 257 rebasing 14:9e36056a46e3 "D" (tip)
258 258 $ hg log --style default --debug -r tip
259 259 changeset: 15:627d4614809036ba22b9e7cb31638ddc06ab99ab
260 260 tag: tip
261 261 phase: draft
262 262 parent: 4:9520eea781bcca16c1e15acc0ba14335a0e8e5ba
263 263 parent: -1:0000000000000000000000000000000000000000
264 264 manifest: 15:648e8ede73ae3e497d093d3a4c8fcc2daa864f42
265 265 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
266 266 date: Sat Apr 30 15:24:48 2011 +0200
267 267 files+: D
268 268 extra: branch=default
269 269 extra: intermediate-source=4596109a6a4328c398bde3a4a3b6737cfade3003
270 270 extra: rebase_source=9e36056a46e37c9776168c7375734eebc70e294f
271 271 extra: source=32af7686d403cf45b5d95f2d70cebea587ac806a
272 272 description:
273 273 D
274 274
275 275
276 276 Start rebase from a commit that is obsolete but not hidden only because it's
277 277 a working copy parent. We should be moved back to the starting commit as usual
278 278 even though it is hidden (until we're moved there).
279 279
280 280 $ hg --hidden up -qr 'first(hidden())'
281 281 $ hg rebase --rev 13 --dest 15
282 282 rebasing 13:98f6af4ee953 "C"
283 283 $ hg log -G
284 284 o 16:294a2b93eb4d C
285 285 |
286 286 o 15:627d46148090 D
287 287 |
288 288 | o 12:462a34d07e59 B
289 289 | |
290 290 | o 11:4596109a6a43 D
291 291 | |
292 292 | o 7:02de42196ebe H
293 293 | |
294 294 +---o 6:eea13746799a G
295 295 | |/
296 296 | o 5:24b6387c8c8c F
297 297 | |
298 298 o | 4:9520eea781bc E
299 299 |/
300 300 | @ 1:42ccdea3bb16 B
301 301 |/
302 302 o 0:cd010b8cd998 A
303 303
304 304
305 305 $ cd ..
306 306
307 307 collapse rebase
308 308 ---------------------------------
309 309
310 310 $ hg clone base collapse
311 311 updating to branch default
312 312 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
313 313 $ cd collapse
314 314 $ hg rebase -s 42ccdea3bb16 -d eea13746799a --collapse
315 315 rebasing 1:42ccdea3bb16 "B"
316 316 rebasing 2:5fddd98957c8 "C"
317 317 rebasing 3:32af7686d403 "D"
318 318 $ hg log -G
319 319 o 8:4dc2197e807b Collapsed revision
320 320 |
321 321 | @ 7:02de42196ebe H
322 322 | |
323 323 o | 6:eea13746799a G
324 324 |\|
325 325 | o 5:24b6387c8c8c F
326 326 | |
327 327 o | 4:9520eea781bc E
328 328 |/
329 329 o 0:cd010b8cd998 A
330 330
331 331 $ hg log --hidden -G
332 332 o 8:4dc2197e807b Collapsed revision
333 333 |
334 334 | @ 7:02de42196ebe H
335 335 | |
336 336 o | 6:eea13746799a G
337 337 |\|
338 338 | o 5:24b6387c8c8c F
339 339 | |
340 340 o | 4:9520eea781bc E
341 341 |/
342 342 | x 3:32af7686d403 D
343 343 | |
344 344 | x 2:5fddd98957c8 C
345 345 | |
346 346 | x 1:42ccdea3bb16 B
347 347 |/
348 348 o 0:cd010b8cd998 A
349 349
350 350 $ hg id --debug -r tip
351 351 4dc2197e807bae9817f09905b50ab288be2dbbcf tip
352 352 $ hg debugobsolete
353 353 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
354 354 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
355 355 32af7686d403cf45b5d95f2d70cebea587ac806a 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
356 356
357 357 $ cd ..
358 358
359 359 Rebase set has hidden descendants
360 360 ---------------------------------
361 361
362 362 We rebase a changeset which has a hidden changeset. The hidden changeset must
363 363 not be rebased.
364 364
365 365 $ hg clone base hidden
366 366 updating to branch default
367 367 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
368 368 $ cd hidden
369 369 $ hg rebase -s 5fddd98957c8 -d eea13746799a
370 370 rebasing 2:5fddd98957c8 "C"
371 371 rebasing 3:32af7686d403 "D"
372 372 $ hg rebase -s 42ccdea3bb16 -d 02de42196ebe
373 373 rebasing 1:42ccdea3bb16 "B"
374 374 $ hg log -G
375 375 o 10:7c6027df6a99 B
376 376 |
377 377 | o 9:cf44d2f5a9f4 D
378 378 | |
379 379 | o 8:e273c5e7d2d2 C
380 380 | |
381 381 @ | 7:02de42196ebe H
382 382 | |
383 383 | o 6:eea13746799a G
384 384 |/|
385 385 o | 5:24b6387c8c8c F
386 386 | |
387 387 | o 4:9520eea781bc E
388 388 |/
389 389 o 0:cd010b8cd998 A
390 390
391 391 $ hg log --hidden -G
392 392 o 10:7c6027df6a99 B
393 393 |
394 394 | o 9:cf44d2f5a9f4 D
395 395 | |
396 396 | o 8:e273c5e7d2d2 C
397 397 | |
398 398 @ | 7:02de42196ebe H
399 399 | |
400 400 | o 6:eea13746799a G
401 401 |/|
402 402 o | 5:24b6387c8c8c F
403 403 | |
404 404 | o 4:9520eea781bc E
405 405 |/
406 406 | x 3:32af7686d403 D
407 407 | |
408 408 | x 2:5fddd98957c8 C
409 409 | |
410 410 | x 1:42ccdea3bb16 B
411 411 |/
412 412 o 0:cd010b8cd998 A
413 413
414 414 $ hg debugobsolete
415 415 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b e273c5e7d2d29df783dce9f9eaa3ac4adc69c15d 0 (*) {'user': 'test'} (glob)
416 416 32af7686d403cf45b5d95f2d70cebea587ac806a cf44d2f5a9f4297a62be94cbdd3dff7c7dc54258 0 (*) {'user': 'test'} (glob)
417 417 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 7c6027df6a99d93f461868e5433f63bde20b6dfb 0 (*) {'user': 'test'} (glob)
418 418
419 419 Test that rewriting leaving instability behind is allowed
420 420 ---------------------------------------------------------------------
421 421
422 422 $ hg log -r 'children(8)'
423 423 9:cf44d2f5a9f4 D (no-eol)
424 424 $ hg rebase -r 8
425 425 rebasing 8:e273c5e7d2d2 "C"
426 426 $ hg log -G
427 427 o 11:0d8f238b634c C
428 428 |
429 429 o 10:7c6027df6a99 B
430 430 |
431 431 | o 9:cf44d2f5a9f4 D
432 432 | |
433 433 | x 8:e273c5e7d2d2 C
434 434 | |
435 435 @ | 7:02de42196ebe H
436 436 | |
437 437 | o 6:eea13746799a G
438 438 |/|
439 439 o | 5:24b6387c8c8c F
440 440 | |
441 441 | o 4:9520eea781bc E
442 442 |/
443 443 o 0:cd010b8cd998 A
444 444
445 445
446 446
447 447 Test multiple root handling
448 448 ------------------------------------
449 449
450 450 $ hg rebase --dest 4 --rev '7+11+9'
451 451 rebasing 9:cf44d2f5a9f4 "D"
452 452 rebasing 7:02de42196ebe "H"
453 453 rebasing 11:0d8f238b634c "C" (tip)
454 454 $ hg log -G
455 455 o 14:1e8370e38cca C
456 456 |
457 457 @ 13:bfe264faf697 H
458 458 |
459 459 | o 12:102b4c1d889b D
460 460 |/
461 461 | o 10:7c6027df6a99 B
462 462 | |
463 463 | x 7:02de42196ebe H
464 464 | |
465 465 +---o 6:eea13746799a G
466 466 | |/
467 467 | o 5:24b6387c8c8c F
468 468 | |
469 469 o | 4:9520eea781bc E
470 470 |/
471 471 o 0:cd010b8cd998 A
472 472
473 473 $ cd ..
474 474
475 475 Detach both parents
476 476
477 477 $ hg init double-detach
478 478 $ cd double-detach
479 479
480 480 $ hg debugdrawdag <<EOF
481 481 > F
482 482 > /|
483 483 > C E
484 484 > | |
485 485 > B D G
486 486 > \|/
487 487 > A
488 488 > EOF
489 489
490 490 $ hg rebase -d G -r 'B + D + F'
491 491 rebasing 1:112478962961 "B" (B)
492 492 rebasing 2:b18e25de2cf5 "D" (D)
493 493 rebasing 6:f15c3adaf214 "F" (F tip)
494 494 abort: cannot rebase 6:f15c3adaf214 without moving at least one of its parents
495 495 [255]
496 496
497 497 $ cd ..
498 498
499 499 test on rebase dropping a merge
500 500
501 501 (setup)
502 502
503 503 $ hg init dropmerge
504 504 $ cd dropmerge
505 505 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
506 506 adding changesets
507 507 adding manifests
508 508 adding file changes
509 509 added 8 changesets with 7 changes to 7 files (+2 heads)
510 510 (run 'hg heads' to see heads, 'hg merge' to merge)
511 511 $ hg up 3
512 512 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
513 513 $ hg merge 7
514 514 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
515 515 (branch merge, don't forget to commit)
516 516 $ hg ci -m 'M'
517 517 $ echo I > I
518 518 $ hg add I
519 519 $ hg ci -m I
520 520 $ hg log -G
521 521 @ 9:4bde274eefcf I
522 522 |
523 523 o 8:53a6a128b2b7 M
524 524 |\
525 525 | o 7:02de42196ebe H
526 526 | |
527 527 | | o 6:eea13746799a G
528 528 | |/|
529 529 | o | 5:24b6387c8c8c F
530 530 | | |
531 531 | | o 4:9520eea781bc E
532 532 | |/
533 533 o | 3:32af7686d403 D
534 534 | |
535 535 o | 2:5fddd98957c8 C
536 536 | |
537 537 o | 1:42ccdea3bb16 B
538 538 |/
539 539 o 0:cd010b8cd998 A
540 540
541 541 (actual test)
542 542
543 543 $ hg rebase --dest 6 --rev '((desc(H) + desc(D))::) - desc(M)'
544 544 rebasing 3:32af7686d403 "D"
545 545 rebasing 7:02de42196ebe "H"
546 546 rebasing 9:4bde274eefcf "I" (tip)
547 547 $ hg log -G
548 548 @ 12:acd174b7ab39 I
549 549 |
550 550 o 11:6c11a6218c97 H
551 551 |
552 552 | o 10:b5313c85b22e D
553 553 |/
554 554 | o 8:53a6a128b2b7 M
555 555 | |\
556 556 | | x 7:02de42196ebe H
557 557 | | |
558 558 o---+ 6:eea13746799a G
559 559 | | |
560 560 | | o 5:24b6387c8c8c F
561 561 | | |
562 562 o---+ 4:9520eea781bc E
563 563 / /
564 564 x | 3:32af7686d403 D
565 565 | |
566 566 o | 2:5fddd98957c8 C
567 567 | |
568 568 o | 1:42ccdea3bb16 B
569 569 |/
570 570 o 0:cd010b8cd998 A
571 571
572 572
573 573 Test hidden changesets in the rebase set (issue4504)
574 574
575 575 $ hg up --hidden 9
576 576 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
577 577 $ echo J > J
578 578 $ hg add J
579 579 $ hg commit -m J
580 580 $ hg debugobsolete `hg log --rev . -T '{node}'`
581 581 obsoleted 1 changesets
582 582
583 583 $ hg rebase --rev .~1::. --dest 'max(desc(D))' --traceback --config experimental.rebaseskipobsolete=off
584 584 rebasing 9:4bde274eefcf "I"
585 585 rebasing 13:06edfc82198f "J" (tip)
586 586 $ hg log -G
587 587 @ 15:5ae8a643467b J
588 588 |
589 589 o 14:9ad579b4a5de I
590 590 |
591 591 | o 12:acd174b7ab39 I
592 592 | |
593 593 | o 11:6c11a6218c97 H
594 594 | |
595 595 o | 10:b5313c85b22e D
596 596 |/
597 597 | o 8:53a6a128b2b7 M
598 598 | |\
599 599 | | x 7:02de42196ebe H
600 600 | | |
601 601 o---+ 6:eea13746799a G
602 602 | | |
603 603 | | o 5:24b6387c8c8c F
604 604 | | |
605 605 o---+ 4:9520eea781bc E
606 606 / /
607 607 x | 3:32af7686d403 D
608 608 | |
609 609 o | 2:5fddd98957c8 C
610 610 | |
611 611 o | 1:42ccdea3bb16 B
612 612 |/
613 613 o 0:cd010b8cd998 A
614 614
615 615 $ hg up 14 -C
616 616 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
617 617 $ echo "K" > K
618 618 $ hg add K
619 619 $ hg commit --amend -m "K"
620 620 $ echo "L" > L
621 621 $ hg add L
622 622 $ hg commit -m "L"
623 623 $ hg up '.^'
624 624 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
625 625 $ echo "M" > M
626 626 $ hg add M
627 627 $ hg commit --amend -m "M"
628 628 $ hg log -G
629 629 @ 20:bfaedf8eb73b M
630 630 |
631 631 | o 18:97219452e4bd L
632 632 | |
633 633 | x 17:fc37a630c901 K
634 634 |/
635 635 | o 15:5ae8a643467b J
636 636 | |
637 637 | x 14:9ad579b4a5de I
638 638 |/
639 639 | o 12:acd174b7ab39 I
640 640 | |
641 641 | o 11:6c11a6218c97 H
642 642 | |
643 643 o | 10:b5313c85b22e D
644 644 |/
645 645 | o 8:53a6a128b2b7 M
646 646 | |\
647 647 | | x 7:02de42196ebe H
648 648 | | |
649 649 o---+ 6:eea13746799a G
650 650 | | |
651 651 | | o 5:24b6387c8c8c F
652 652 | | |
653 653 o---+ 4:9520eea781bc E
654 654 / /
655 655 x | 3:32af7686d403 D
656 656 | |
657 657 o | 2:5fddd98957c8 C
658 658 | |
659 659 o | 1:42ccdea3bb16 B
660 660 |/
661 661 o 0:cd010b8cd998 A
662 662
663 663 $ hg rebase -s 14 -d 18 --config experimental.rebaseskipobsolete=True
664 664 note: not rebasing 14:9ad579b4a5de "I", already in destination as 17:fc37a630c901 "K"
665 665 rebasing 15:5ae8a643467b "J"
666 666
667 667 $ cd ..
668 668
669 669 Skip obsolete changeset even with multiple hops
670 670 -----------------------------------------------
671 671
672 672 setup
673 673
674 674 $ hg init obsskip
675 675 $ cd obsskip
676 676 $ cat << EOF >> .hg/hgrc
677 677 > [experimental]
678 678 > rebaseskipobsolete = True
679 679 > [extensions]
680 680 > strip =
681 681 > EOF
682 682 $ echo A > A
683 683 $ hg add A
684 684 $ hg commit -m A
685 685 $ echo B > B
686 686 $ hg add B
687 687 $ hg commit -m B0
688 688 $ hg commit --amend -m B1
689 689 $ hg commit --amend -m B2
690 690 $ hg up --hidden 'desc(B0)'
691 691 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
692 692 $ echo C > C
693 693 $ hg add C
694 694 $ hg commit -m C
695 695
696 696 Rebase finds its way in a chain of marker
697 697
698 698 $ hg rebase -d 'desc(B2)'
699 699 note: not rebasing 1:a8b11f55fb19 "B0", already in destination as 3:261e70097290 "B2"
700 700 rebasing 4:212cb178bcbb "C" (tip)
701 701
702 702 Even when the chain include missing node
703 703
704 704 $ hg up --hidden 'desc(B0)'
705 705 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
706 706 $ echo D > D
707 707 $ hg add D
708 708 $ hg commit -m D
709 709 $ hg --hidden strip -r 'desc(B1)'
710 710 saved backup bundle to $TESTTMP/obsskip/.hg/strip-backup/86f6414ccda7-b1c452ee-backup.hg (glob)
711 711
712 712 $ hg rebase -d 'desc(B2)'
713 713 note: not rebasing 1:a8b11f55fb19 "B0", already in destination as 2:261e70097290 "B2"
714 714 rebasing 5:1a79b7535141 "D" (tip)
715 715 $ hg up 4
716 716 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
717 717 $ echo "O" > O
718 718 $ hg add O
719 719 $ hg commit -m O
720 720 $ echo "P" > P
721 721 $ hg add P
722 722 $ hg commit -m P
723 723 $ hg log -G
724 724 @ 8:8d47583e023f P
725 725 |
726 726 o 7:360bbaa7d3ce O
727 727 |
728 728 | o 6:9c48361117de D
729 729 | |
730 730 o | 4:ff2c4d47b71d C
731 731 |/
732 732 o 2:261e70097290 B2
733 733 |
734 734 o 0:4a2df7238c3b A
735 735
736 736 $ hg debugobsolete `hg log -r 7 -T '{node}\n'` --config experimental.stabilization=all
737 737 obsoleted 1 changesets
738 738 $ hg rebase -d 6 -r "4::"
739 rebasing 4:ff2c4d47b71d "C"
739 740 note: not rebasing 7:360bbaa7d3ce "O", it has no successor
740 rebasing 4:ff2c4d47b71d "C"
741 741 rebasing 8:8d47583e023f "P" (tip)
742 742
743 743 If all the changeset to be rebased are obsolete and present in the destination, we
744 744 should display a friendly error message
745 745
746 746 $ hg log -G
747 747 @ 10:121d9e3bc4c6 P
748 748 |
749 749 o 9:4be60e099a77 C
750 750 |
751 751 o 6:9c48361117de D
752 752 |
753 753 o 2:261e70097290 B2
754 754 |
755 755 o 0:4a2df7238c3b A
756 756
757 757
758 758 $ hg up 9
759 759 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
760 760 $ echo "non-relevant change" > nonrelevant
761 761 $ hg add nonrelevant
762 762 $ hg commit -m nonrelevant
763 763 created new head
764 764 $ hg debugobsolete `hg log -r 11 -T '{node}\n'` --config experimental.stabilization=all
765 765 obsoleted 1 changesets
766 766 $ hg rebase -r . -d 10
767 767 note: not rebasing 11:f44da1f4954c "nonrelevant" (tip), it has no successor
768 768
769 769 If a rebase is going to create divergence, it should abort
770 770
771 771 $ hg log -G
772 @ 11:f44da1f4954c nonrelevant
772 @ 10:121d9e3bc4c6 P
773 773 |
774 | o 10:121d9e3bc4c6 P
775 |/
776 774 o 9:4be60e099a77 C
777 775 |
778 776 o 6:9c48361117de D
779 777 |
780 778 o 2:261e70097290 B2
781 779 |
782 780 o 0:4a2df7238c3b A
783 781
784 782
785 783 $ hg up 9
786 784 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
787 785 $ echo "john" > doe
788 786 $ hg add doe
789 787 $ hg commit -m "john doe"
790 788 created new head
791 789 $ hg up 10
792 790 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
793 791 $ echo "foo" > bar
794 792 $ hg add bar
795 793 $ hg commit --amend -m "10'"
796 794 $ hg up 10 --hidden
797 795 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
798 796 $ echo "bar" > foo
799 797 $ hg add foo
800 798 $ hg commit -m "bar foo"
801 799 $ hg log -G
802 800 @ 15:73568ab6879d bar foo
803 801 |
804 802 | o 14:77d874d096a2 10'
805 803 | |
806 804 | | o 12:3eb461388009 john doe
807 805 | |/
808 806 x | 10:121d9e3bc4c6 P
809 807 |/
810 808 o 9:4be60e099a77 C
811 809 |
812 810 o 6:9c48361117de D
813 811 |
814 812 o 2:261e70097290 B2
815 813 |
816 814 o 0:4a2df7238c3b A
817 815
818 816 $ hg summary
819 817 parent: 15:73568ab6879d tip (orphan)
820 818 bar foo
821 819 branch: default
822 820 commit: (clean)
823 821 update: 2 new changesets, 3 branch heads (merge)
824 822 phases: 8 draft
825 823 orphan: 1 changesets
826 824 $ hg rebase -s 10 -d 12
827 825 abort: this rebase will cause divergences from: 121d9e3bc4c6
828 826 (to force the rebase please set experimental.allowdivergence=True)
829 827 [255]
830 828 $ hg log -G
831 829 @ 15:73568ab6879d bar foo
832 830 |
833 831 | o 14:77d874d096a2 10'
834 832 | |
835 833 | | o 12:3eb461388009 john doe
836 834 | |/
837 835 x | 10:121d9e3bc4c6 P
838 836 |/
839 837 o 9:4be60e099a77 C
840 838 |
841 839 o 6:9c48361117de D
842 840 |
843 841 o 2:261e70097290 B2
844 842 |
845 843 o 0:4a2df7238c3b A
846 844
847 845 With experimental.allowdivergence=True, rebase can create divergence
848 846
849 847 $ hg rebase -s 10 -d 12 --config experimental.allowdivergence=True
850 848 rebasing 10:121d9e3bc4c6 "P"
851 849 rebasing 15:73568ab6879d "bar foo" (tip)
852 850 $ hg summary
853 851 parent: 17:61bd55f69bc4 tip
854 852 bar foo
855 853 branch: default
856 854 commit: (clean)
857 855 update: 1 new changesets, 2 branch heads (merge)
858 856 phases: 8 draft
859 857 content-divergent: 2 changesets
860 858
861 859 rebase --continue + skipped rev because their successors are in destination
862 860 we make a change in trunk and work on conflicting changes to make rebase abort.
863 861
864 862 $ hg log -G -r 17::
865 863 @ 17:61bd55f69bc4 bar foo
866 864 |
867 865 ~
868 866
869 867 Create the two changes in trunk
870 868 $ printf "a" > willconflict
871 869 $ hg add willconflict
872 870 $ hg commit -m "willconflict first version"
873 871
874 872 $ printf "dummy" > C
875 873 $ hg commit -m "dummy change successor"
876 874
877 875 Create the changes that we will rebase
878 876 $ hg update -C 17 -q
879 877 $ printf "b" > willconflict
880 878 $ hg add willconflict
881 879 $ hg commit -m "willconflict second version"
882 880 created new head
883 881 $ printf "dummy" > K
884 882 $ hg add K
885 883 $ hg commit -m "dummy change"
886 884 $ printf "dummy" > L
887 885 $ hg add L
888 886 $ hg commit -m "dummy change"
889 887 $ hg debugobsolete `hg log -r ".^" -T '{node}'` `hg log -r 19 -T '{node}'` --config experimental.stabilization=all
890 888 obsoleted 1 changesets
891 889
892 890 $ hg log -G -r 17::
893 891 @ 22:7bdc8a87673d dummy change
894 892 |
895 893 x 21:8b31da3c4919 dummy change
896 894 |
897 895 o 20:b82fb57ea638 willconflict second version
898 896 |
899 897 | o 19:601db7a18f51 dummy change successor
900 898 | |
901 899 | o 18:357ddf1602d5 willconflict first version
902 900 |/
903 901 o 17:61bd55f69bc4 bar foo
904 902 |
905 903 ~
906 904 $ hg rebase -r ".^^ + .^ + ." -d 19
907 note: not rebasing 21:8b31da3c4919 "dummy change", already in destination as 19:601db7a18f51 "dummy change successor"
908 905 rebasing 20:b82fb57ea638 "willconflict second version"
909 906 merging willconflict
910 907 warning: conflicts while merging willconflict! (edit, then use 'hg resolve --mark')
911 908 unresolved conflicts (see hg resolve, then hg rebase --continue)
912 909 [1]
913 910
914 911 $ hg resolve --mark willconflict
915 912 (no more unresolved files)
916 913 continue: hg rebase --continue
917 914 $ hg rebase --continue
918 915 rebasing 20:b82fb57ea638 "willconflict second version"
916 note: not rebasing 21:8b31da3c4919 "dummy change", already in destination as 19:601db7a18f51 "dummy change successor"
919 917 rebasing 22:7bdc8a87673d "dummy change" (tip)
920 918 $ cd ..
921 919
922 920 Rebase merge where successor of one parent is equal to destination (issue5198)
923 921
924 922 $ hg init p1-succ-is-dest
925 923 $ cd p1-succ-is-dest
926 924
927 925 $ hg debugdrawdag <<EOF
928 926 > F
929 927 > /|
930 928 > E D B # replace: D -> B
931 929 > \|/
932 930 > A
933 931 > EOF
934 932
935 933 $ hg rebase -d B -s D
936 934 note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B"
937 935 rebasing 4:66f1a38021c9 "F" (F tip)
938 936 $ hg log -G
939 937 o 5:50e9d60b99c6 F
940 938 |\
941 939 | | x 4:66f1a38021c9 F
942 940 | |/|
943 941 | o | 3:7fb047a69f22 E
944 942 | | |
945 943 | | x 2:b18e25de2cf5 D
946 944 | |/
947 945 o | 1:112478962961 B
948 946 |/
949 947 o 0:426bada5c675 A
950 948
951 949 $ cd ..
952 950
953 951 Rebase merge where successor of other parent is equal to destination
954 952
955 953 $ hg init p2-succ-is-dest
956 954 $ cd p2-succ-is-dest
957 955
958 956 $ hg debugdrawdag <<EOF
959 957 > F
960 958 > /|
961 959 > E D B # replace: E -> B
962 960 > \|/
963 961 > A
964 962 > EOF
965 963
966 964 $ hg rebase -d B -s E
967 965 note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B"
968 966 rebasing 4:66f1a38021c9 "F" (F tip)
969 967 $ hg log -G
970 968 o 5:aae1787dacee F
971 969 |\
972 970 | | x 4:66f1a38021c9 F
973 971 | |/|
974 972 | | x 3:7fb047a69f22 E
975 973 | | |
976 974 | o | 2:b18e25de2cf5 D
977 975 | |/
978 976 o / 1:112478962961 B
979 977 |/
980 978 o 0:426bada5c675 A
981 979
982 980 $ cd ..
983 981
984 982 Rebase merge where successor of one parent is ancestor of destination
985 983
986 984 $ hg init p1-succ-in-dest
987 985 $ cd p1-succ-in-dest
988 986
989 987 $ hg debugdrawdag <<EOF
990 988 > F C
991 989 > /| |
992 990 > E D B # replace: D -> B
993 991 > \|/
994 992 > A
995 993 > EOF
996 994
997 995 $ hg rebase -d C -s D
998 996 note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B"
999 997 rebasing 5:66f1a38021c9 "F" (F tip)
1000 998
1001 999 $ hg log -G
1002 1000 o 6:0913febf6439 F
1003 1001 |\
1004 1002 +---x 5:66f1a38021c9 F
1005 1003 | | |
1006 1004 | o | 4:26805aba1e60 C
1007 1005 | | |
1008 1006 o | | 3:7fb047a69f22 E
1009 1007 | | |
1010 1008 +---x 2:b18e25de2cf5 D
1011 1009 | |
1012 1010 | o 1:112478962961 B
1013 1011 |/
1014 1012 o 0:426bada5c675 A
1015 1013
1016 1014 $ cd ..
1017 1015
1018 1016 Rebase merge where successor of other parent is ancestor of destination
1019 1017
1020 1018 $ hg init p2-succ-in-dest
1021 1019 $ cd p2-succ-in-dest
1022 1020
1023 1021 $ hg debugdrawdag <<EOF
1024 1022 > F C
1025 1023 > /| |
1026 1024 > E D B # replace: E -> B
1027 1025 > \|/
1028 1026 > A
1029 1027 > EOF
1030 1028
1031 1029 $ hg rebase -d C -s E
1032 1030 note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B"
1033 1031 rebasing 5:66f1a38021c9 "F" (F tip)
1034 1032 $ hg log -G
1035 1033 o 6:c6ab0cc6d220 F
1036 1034 |\
1037 1035 +---x 5:66f1a38021c9 F
1038 1036 | | |
1039 1037 | o | 4:26805aba1e60 C
1040 1038 | | |
1041 1039 | | x 3:7fb047a69f22 E
1042 1040 | | |
1043 1041 o---+ 2:b18e25de2cf5 D
1044 1042 / /
1045 1043 o / 1:112478962961 B
1046 1044 |/
1047 1045 o 0:426bada5c675 A
1048 1046
1049 1047 $ cd ..
1050 1048
1051 1049 Rebase merge where successor of one parent is ancestor of destination
1052 1050
1053 1051 $ hg init p1-succ-in-dest-b
1054 1052 $ cd p1-succ-in-dest-b
1055 1053
1056 1054 $ hg debugdrawdag <<EOF
1057 1055 > F C
1058 1056 > /| |
1059 1057 > E D B # replace: E -> B
1060 1058 > \|/
1061 1059 > A
1062 1060 > EOF
1063 1061
1064 1062 $ hg rebase -d C -b F
1063 rebasing 2:b18e25de2cf5 "D" (D)
1065 1064 note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B"
1066 rebasing 2:b18e25de2cf5 "D" (D)
1067 1065 rebasing 5:66f1a38021c9 "F" (F tip)
1068 1066 note: rebase of 5:66f1a38021c9 created no changes to commit
1069 1067 $ hg log -G
1070 1068 o 6:8f47515dda15 D
1071 1069 |
1072 1070 | x 5:66f1a38021c9 F
1073 1071 | |\
1074 1072 o | | 4:26805aba1e60 C
1075 1073 | | |
1076 1074 | | x 3:7fb047a69f22 E
1077 1075 | | |
1078 1076 | x | 2:b18e25de2cf5 D
1079 1077 | |/
1080 1078 o / 1:112478962961 B
1081 1079 |/
1082 1080 o 0:426bada5c675 A
1083 1081
1084 1082 $ cd ..
1085 1083
1086 1084 Rebase merge where successor of other parent is ancestor of destination
1087 1085
1088 1086 $ hg init p2-succ-in-dest-b
1089 1087 $ cd p2-succ-in-dest-b
1090 1088
1091 1089 $ hg debugdrawdag <<EOF
1092 1090 > F C
1093 1091 > /| |
1094 1092 > E D B # replace: D -> B
1095 1093 > \|/
1096 1094 > A
1097 1095 > EOF
1098 1096
1099 1097 $ hg rebase -d C -b F
1100 1098 note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B"
1101 1099 rebasing 3:7fb047a69f22 "E" (E)
1102 1100 rebasing 5:66f1a38021c9 "F" (F tip)
1103 1101 note: rebase of 5:66f1a38021c9 created no changes to commit
1104 1102
1105 1103 $ hg log -G
1106 1104 o 6:533690786a86 E
1107 1105 |
1108 1106 | x 5:66f1a38021c9 F
1109 1107 | |\
1110 1108 o | | 4:26805aba1e60 C
1111 1109 | | |
1112 1110 | | x 3:7fb047a69f22 E
1113 1111 | | |
1114 1112 | x | 2:b18e25de2cf5 D
1115 1113 | |/
1116 1114 o / 1:112478962961 B
1117 1115 |/
1118 1116 o 0:426bada5c675 A
1119 1117
1120 1118 $ cd ..
1121 1119
1122 1120 Rebase merge where both parents have successors in destination
1123 1121
1124 1122 $ hg init p12-succ-in-dest
1125 1123 $ cd p12-succ-in-dest
1126 1124 $ hg debugdrawdag <<'EOS'
1127 1125 > E F
1128 1126 > /| /| # replace: A -> C
1129 1127 > A B C D # replace: B -> D
1130 1128 > | |
1131 1129 > X Y
1132 1130 > EOS
1133 1131 $ hg rebase -r A+B+E -d F
1134 1132 note: not rebasing 4:a3d17304151f "A" (A), already in destination as 0:96cc3511f894 "C"
1135 1133 note: not rebasing 5:b23a2cc00842 "B" (B), already in destination as 1:058c1e1fb10a "D"
1136 1134 rebasing 7:dac5d11c5a7d "E" (E tip)
1137 1135 abort: rebasing 7:dac5d11c5a7d will include unwanted changes from 3:59c792af609c, 5:b23a2cc00842 or 2:ba2b7fa7166d, 4:a3d17304151f
1138 1136 [255]
1139 1137 $ cd ..
1140 1138
1141 1139 Rebase a non-clean merge. One parent has successor in destination, the other
1142 1140 parent moves as requested.
1143 1141
1144 1142 $ hg init p1-succ-p2-move
1145 1143 $ cd p1-succ-p2-move
1146 1144 $ hg debugdrawdag <<'EOS'
1147 1145 > D Z
1148 1146 > /| | # replace: A -> C
1149 1147 > A B C # D/D = D
1150 1148 > EOS
1151 1149 $ hg rebase -r A+B+D -d Z
1152 1150 note: not rebasing 0:426bada5c675 "A" (A), already in destination as 2:96cc3511f894 "C"
1153 1151 rebasing 1:fc2b737bb2e5 "B" (B)
1154 1152 rebasing 3:b8ed089c80ad "D" (D)
1155 1153
1156 1154 $ rm .hg/localtags
1157 1155 $ hg log -G
1158 1156 o 6:e4f78693cc88 D
1159 1157 |
1160 1158 o 5:76840d832e98 B
1161 1159 |
1162 1160 o 4:50e41c1f3950 Z
1163 1161 |
1164 1162 o 2:96cc3511f894 C
1165 1163
1166 1164 $ hg files -r tip
1167 1165 B
1168 1166 C
1169 1167 D
1170 1168 Z
1171 1169
1172 1170 $ cd ..
1173 1171
1174 1172 $ hg init p1-move-p2-succ
1175 1173 $ cd p1-move-p2-succ
1176 1174 $ hg debugdrawdag <<'EOS'
1177 1175 > D Z
1178 1176 > /| | # replace: B -> C
1179 1177 > A B C # D/D = D
1180 1178 > EOS
1181 1179 $ hg rebase -r B+A+D -d Z
1180 rebasing 0:426bada5c675 "A" (A)
1182 1181 note: not rebasing 1:fc2b737bb2e5 "B" (B), already in destination as 2:96cc3511f894 "C"
1183 rebasing 0:426bada5c675 "A" (A)
1184 1182 rebasing 3:b8ed089c80ad "D" (D)
1185 1183
1186 1184 $ rm .hg/localtags
1187 1185 $ hg log -G
1188 1186 o 6:1b355ed94d82 D
1189 1187 |
1190 1188 o 5:a81a74d764a6 A
1191 1189 |
1192 1190 o 4:50e41c1f3950 Z
1193 1191 |
1194 1192 o 2:96cc3511f894 C
1195 1193
1196 1194 $ hg files -r tip
1197 1195 A
1198 1196 C
1199 1197 D
1200 1198 Z
1201 1199
1202 1200 $ cd ..
1203 1201
1204 1202 Test that bookmark is moved and working dir is updated when all changesets have
1205 1203 equivalents in destination
1206 1204 $ hg init rbsrepo && cd rbsrepo
1207 1205 $ echo "[experimental]" > .hg/hgrc
1208 1206 $ echo "stabilization=all" >> .hg/hgrc
1209 1207 $ echo "rebaseskipobsolete=on" >> .hg/hgrc
1210 1208 $ echo root > root && hg ci -Am root
1211 1209 adding root
1212 1210 $ echo a > a && hg ci -Am a
1213 1211 adding a
1214 1212 $ hg up 0
1215 1213 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1216 1214 $ echo b > b && hg ci -Am b
1217 1215 adding b
1218 1216 created new head
1219 1217 $ hg rebase -r 2 -d 1
1220 1218 rebasing 2:1e9a3c00cbe9 "b" (tip)
1221 1219 $ hg log -r . # working dir is at rev 3 (successor of 2)
1222 1220 3:be1832deae9a b (no-eol)
1223 1221 $ hg book -r 2 mybook --hidden # rev 2 has a bookmark on it now
1224 1222 $ hg up 2 && hg log -r . # working dir is at rev 2 again
1225 1223 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1226 1224 2:1e9a3c00cbe9 b (no-eol)
1227 1225 $ hg rebase -r 2 -d 3 --config experimental.stabilization.track-operation=1
1228 1226 note: not rebasing 2:1e9a3c00cbe9 "b" (mybook), already in destination as 3:be1832deae9a "b"
1229 Check that working directory was not updated to rev 3 because rev 2 was skipped
1230 during the rebase operation
1227 Check that working directory and bookmark was updated to rev 3 although rev 2
1228 was skipped
1231 1229 $ hg log -r .
1232 2:1e9a3c00cbe9 b (no-eol)
1233
1234 Check that bookmark was not moved to rev 3 if rev 2 was skipped during the
1235 rebase operation. This makes sense because if rev 2 has a successor, the
1236 operation generating that successor (ex. rebase) should be responsible for
1237 moving bookmarks. If the bookmark is on a precursor, like rev 2, that means the
1238 user manually moved it back. In that case we should not move it again.
1230 3:be1832deae9a b (no-eol)
1239 1231 $ hg bookmarks
1240 mybook 2:1e9a3c00cbe9
1232 mybook 3:be1832deae9a
1241 1233 $ hg debugobsolete --rev tip
1242 1234 1e9a3c00cbe90d236ac05ef61efcc5e40b7412bc be1832deae9ac531caa7438b8dcf6055a122cd8e 0 (*) {'user': 'test'} (glob)
1235
1236 Obsoleted working parent and bookmark could be moved if an ancestor of working
1237 parent gets moved:
1238
1239 $ hg init $TESTTMP/ancestor-wd-move
1240 $ cd $TESTTMP/ancestor-wd-move
1241 $ hg debugdrawdag <<'EOS'
1242 > E D1 # rebase: D1 -> D2
1243 > | |
1244 > | C
1245 > D2 |
1246 > | B
1247 > |/
1248 > A
1249 > EOS
1250 $ hg update D1 -q
1251 $ hg bookmark book -i
1252 $ hg rebase -r B+D1 -d E
1253 rebasing 1:112478962961 "B" (B)
1254 note: not rebasing 5:15ecf15e0114 "D1" (D1 tip book), already in destination as 2:0807738e0be9 "D2"
1255 $ hg log -G -T '{desc} {bookmarks}'
1256 @ B book
1257 |
1258 | x D1
1259 | |
1260 o | E
1261 | |
1262 | o C
1263 | |
1264 o | D2
1265 | |
1266 | x B
1267 |/
1268 o A
1269
General Comments 0
You need to be logged in to leave comments. Login now