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