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