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