##// END OF EJS Templates
rebase: abort if *any* commit in rebase set is public
Martin von Zweigbergk -
r31302:681046de default
parent child Browse files
Show More
@@ -1,1483 +1,1483
1 1 # rebase.py - rebasing feature for mercurial
2 2 #
3 3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''command to move sets of revisions to a different ancestor
9 9
10 10 This extension lets you rebase changesets in an existing Mercurial
11 11 repository.
12 12
13 13 For more information:
14 14 https://mercurial-scm.org/wiki/RebaseExtension
15 15 '''
16 16
17 17 from __future__ import absolute_import
18 18
19 19 import errno
20 20 import os
21 21
22 22 from mercurial.i18n import _
23 23 from mercurial.node import (
24 24 hex,
25 25 nullid,
26 26 nullrev,
27 27 short,
28 28 )
29 29 from mercurial import (
30 30 bookmarks,
31 31 cmdutil,
32 32 commands,
33 33 copies,
34 34 destutil,
35 35 dirstateguard,
36 36 error,
37 37 extensions,
38 38 hg,
39 39 lock,
40 40 merge as mergemod,
41 41 mergeutil,
42 42 obsolete,
43 43 patch,
44 44 phases,
45 45 registrar,
46 46 repair,
47 47 repoview,
48 48 revset,
49 49 scmutil,
50 50 smartset,
51 51 util,
52 52 )
53 53
54 54 release = lock.release
55 55 templateopts = commands.templateopts
56 56
57 57 # The following constants are used throughout the rebase module. The ordering of
58 58 # their values must be maintained.
59 59
60 60 # Indicates that a revision needs to be rebased
61 61 revtodo = -1
62 62 nullmerge = -2
63 63 revignored = -3
64 64 # successor in rebase destination
65 65 revprecursor = -4
66 66 # plain prune (no successor)
67 67 revpruned = -5
68 68 revskipped = (revignored, revprecursor, revpruned)
69 69
70 70 cmdtable = {}
71 71 command = cmdutil.command(cmdtable)
72 72 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
73 73 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
74 74 # be specifying the version(s) of Mercurial they are tested with, or
75 75 # leave the attribute unspecified.
76 76 testedwith = 'ships-with-hg-core'
77 77
78 78 def _nothingtorebase():
79 79 return 1
80 80
81 81 def _savegraft(ctx, extra):
82 82 s = ctx.extra().get('source', None)
83 83 if s is not None:
84 84 extra['source'] = s
85 85 s = ctx.extra().get('intermediate-source', None)
86 86 if s is not None:
87 87 extra['intermediate-source'] = s
88 88
89 89 def _savebranch(ctx, extra):
90 90 extra['branch'] = ctx.branch()
91 91
92 92 def _makeextrafn(copiers):
93 93 """make an extrafn out of the given copy-functions.
94 94
95 95 A copy function takes a context and an extra dict, and mutates the
96 96 extra dict as needed based on the given context.
97 97 """
98 98 def extrafn(ctx, extra):
99 99 for c in copiers:
100 100 c(ctx, extra)
101 101 return extrafn
102 102
103 103 def _destrebase(repo, sourceset, destspace=None):
104 104 """small wrapper around destmerge to pass the right extra args
105 105
106 106 Please wrap destutil.destmerge instead."""
107 107 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
108 108 onheadcheck=False, destspace=destspace)
109 109
110 110 revsetpredicate = registrar.revsetpredicate()
111 111
112 112 @revsetpredicate('_destrebase')
113 113 def _revsetdestrebase(repo, subset, x):
114 114 # ``_rebasedefaultdest()``
115 115
116 116 # default destination for rebase.
117 117 # # XXX: Currently private because I expect the signature to change.
118 118 # # XXX: - bailing out in case of ambiguity vs returning all data.
119 119 # i18n: "_rebasedefaultdest" is a keyword
120 120 sourceset = None
121 121 if x is not None:
122 122 sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
123 123 return subset & smartset.baseset([_destrebase(repo, sourceset)])
124 124
125 125 class rebaseruntime(object):
126 126 """This class is a container for rebase runtime state"""
127 127 def __init__(self, repo, ui, opts=None):
128 128 if opts is None:
129 129 opts = {}
130 130
131 131 self.repo = repo
132 132 self.ui = ui
133 133 self.opts = opts
134 134 self.originalwd = None
135 135 self.external = nullrev
136 136 # Mapping between the old revision id and either what is the new rebased
137 137 # revision or what needs to be done with the old revision. The state
138 138 # dict will be what contains most of the rebase progress state.
139 139 self.state = {}
140 140 self.activebookmark = None
141 141 self.currentbookmarks = None
142 142 self.target = None
143 143 self.skipped = set()
144 144 self.targetancestors = set()
145 145
146 146 self.collapsef = opts.get('collapse', False)
147 147 self.collapsemsg = cmdutil.logmessage(ui, opts)
148 148 self.date = opts.get('date', None)
149 149
150 150 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
151 151 self.extrafns = [_savegraft]
152 152 if e:
153 153 self.extrafns = [e]
154 154
155 155 self.keepf = opts.get('keep', False)
156 156 self.keepbranchesf = opts.get('keepbranches', False)
157 157 # keepopen is not meant for use on the command line, but by
158 158 # other extensions
159 159 self.keepopen = opts.get('keepopen', False)
160 160 self.obsoletenotrebased = {}
161 161
162 162 def storestatus(self, tr=None):
163 163 """Store the current status to allow recovery"""
164 164 if tr:
165 165 tr.addfilegenerator('rebasestate', ('rebasestate',),
166 166 self._writestatus, location='plain')
167 167 else:
168 168 with self.repo.vfs("rebasestate", "w") as f:
169 169 self._writestatus(f)
170 170
171 171 def _writestatus(self, f):
172 172 repo = self.repo
173 173 f.write(repo[self.originalwd].hex() + '\n')
174 174 f.write(repo[self.target].hex() + '\n')
175 175 f.write(repo[self.external].hex() + '\n')
176 176 f.write('%d\n' % int(self.collapsef))
177 177 f.write('%d\n' % int(self.keepf))
178 178 f.write('%d\n' % int(self.keepbranchesf))
179 179 f.write('%s\n' % (self.activebookmark or ''))
180 180 for d, v in self.state.iteritems():
181 181 oldrev = repo[d].hex()
182 182 if v >= 0:
183 183 newrev = repo[v].hex()
184 184 elif v == revtodo:
185 185 # To maintain format compatibility, we have to use nullid.
186 186 # Please do remove this special case when upgrading the format.
187 187 newrev = hex(nullid)
188 188 else:
189 189 newrev = v
190 190 f.write("%s:%s\n" % (oldrev, newrev))
191 191 repo.ui.debug('rebase status stored\n')
192 192
193 193 def restorestatus(self):
194 194 """Restore a previously stored status"""
195 195 repo = self.repo
196 196 keepbranches = None
197 197 target = None
198 198 collapse = False
199 199 external = nullrev
200 200 activebookmark = None
201 201 state = {}
202 202
203 203 try:
204 204 f = repo.vfs("rebasestate")
205 205 for i, l in enumerate(f.read().splitlines()):
206 206 if i == 0:
207 207 originalwd = repo[l].rev()
208 208 elif i == 1:
209 209 target = repo[l].rev()
210 210 elif i == 2:
211 211 external = repo[l].rev()
212 212 elif i == 3:
213 213 collapse = bool(int(l))
214 214 elif i == 4:
215 215 keep = bool(int(l))
216 216 elif i == 5:
217 217 keepbranches = bool(int(l))
218 218 elif i == 6 and not (len(l) == 81 and ':' in l):
219 219 # line 6 is a recent addition, so for backwards
220 220 # compatibility check that the line doesn't look like the
221 221 # oldrev:newrev lines
222 222 activebookmark = l
223 223 else:
224 224 oldrev, newrev = l.split(':')
225 225 if newrev in (str(nullmerge), str(revignored),
226 226 str(revprecursor), str(revpruned)):
227 227 state[repo[oldrev].rev()] = int(newrev)
228 228 elif newrev == nullid:
229 229 state[repo[oldrev].rev()] = revtodo
230 230 # Legacy compat special case
231 231 else:
232 232 state[repo[oldrev].rev()] = repo[newrev].rev()
233 233
234 234 except IOError as err:
235 235 if err.errno != errno.ENOENT:
236 236 raise
237 237 cmdutil.wrongtooltocontinue(repo, _('rebase'))
238 238
239 239 if keepbranches is None:
240 240 raise error.Abort(_('.hg/rebasestate is incomplete'))
241 241
242 242 skipped = set()
243 243 # recompute the set of skipped revs
244 244 if not collapse:
245 245 seen = set([target])
246 246 for old, new in sorted(state.items()):
247 247 if new != revtodo and new in seen:
248 248 skipped.add(old)
249 249 seen.add(new)
250 250 repo.ui.debug('computed skipped revs: %s\n' %
251 251 (' '.join(str(r) for r in sorted(skipped)) or None))
252 252 repo.ui.debug('rebase status resumed\n')
253 253 _setrebasesetvisibility(repo, set(state.keys()) | set([originalwd]))
254 254
255 255 self.originalwd = originalwd
256 256 self.target = target
257 257 self.state = state
258 258 self.skipped = skipped
259 259 self.collapsef = collapse
260 260 self.keepf = keep
261 261 self.keepbranchesf = keepbranches
262 262 self.external = external
263 263 self.activebookmark = activebookmark
264 264
265 265 def _handleskippingobsolete(self, rebaserevs, obsoleterevs, target):
266 266 """Compute structures necessary for skipping obsolete revisions
267 267
268 268 rebaserevs: iterable of all revisions that are to be rebased
269 269 obsoleterevs: iterable of all obsolete revisions in rebaseset
270 270 target: a destination revision for the rebase operation
271 271 """
272 272 self.obsoletenotrebased = {}
273 273 if not self.ui.configbool('experimental', 'rebaseskipobsolete',
274 274 default=True):
275 275 return
276 276 rebaseset = set(rebaserevs)
277 277 obsoleteset = set(obsoleterevs)
278 278 self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
279 279 obsoleteset, target)
280 280 skippedset = set(self.obsoletenotrebased)
281 281 _checkobsrebase(self.repo, self.ui, obsoleteset, rebaseset, skippedset)
282 282
283 283 def _prepareabortorcontinue(self, isabort):
284 284 try:
285 285 self.restorestatus()
286 286 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
287 287 except error.RepoLookupError:
288 288 if isabort:
289 289 clearstatus(self.repo)
290 290 clearcollapsemsg(self.repo)
291 291 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
292 292 ' only broken state is cleared)\n'))
293 293 return 0
294 294 else:
295 295 msg = _('cannot continue inconsistent rebase')
296 296 hint = _('use "hg rebase --abort" to clear broken state')
297 297 raise error.Abort(msg, hint=hint)
298 298 if isabort:
299 299 return abort(self.repo, self.originalwd, self.target,
300 300 self.state, activebookmark=self.activebookmark)
301 301
302 302 obsrevs = (r for r, st in self.state.items() if st == revprecursor)
303 303 self._handleskippingobsolete(self.state.keys(), obsrevs, self.target)
304 304
305 305 def _preparenewrebase(self, dest, rebaseset):
306 306 if dest is None:
307 307 return _nothingtorebase()
308 308
309 309 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
310 310 if (not (self.keepf or allowunstable)
311 311 and self.repo.revs('first(children(%ld) - %ld)',
312 312 rebaseset, rebaseset)):
313 313 raise error.Abort(
314 314 _("can't remove original changesets with"
315 315 " unrebased descendants"),
316 316 hint=_('use --keep to keep original changesets'))
317 317
318 318 obsrevs = _filterobsoleterevs(self.repo, set(rebaseset))
319 319 self._handleskippingobsolete(rebaseset, obsrevs, dest)
320 320
321 321 result = buildstate(self.repo, dest, rebaseset, self.collapsef,
322 322 self.obsoletenotrebased)
323 323
324 324 if not result:
325 325 # Empty state built, nothing to rebase
326 326 self.ui.status(_('nothing to rebase\n'))
327 327 return _nothingtorebase()
328 328
329 root = min(rebaseset)
330 if not self.keepf and not self.repo[root].mutable():
331 raise error.Abort(_("can't rebase public changeset %s")
332 % self.repo[root],
333 hint=_("see 'hg help phases' for details"))
329 for root in self.repo.set('roots(%ld)', rebaseset):
330 if not self.keepf and not root.mutable():
331 raise error.Abort(_("can't rebase public changeset %s")
332 % root,
333 hint=_("see 'hg help phases' for details"))
334 334
335 335 (self.originalwd, self.target, self.state) = result
336 336 if self.collapsef:
337 337 self.targetancestors = self.repo.changelog.ancestors(
338 338 [self.target],
339 339 inclusive=True)
340 340 self.external = externalparent(self.repo, self.state,
341 341 self.targetancestors)
342 342
343 343 if dest.closesbranch() and not self.keepbranchesf:
344 344 self.ui.status(_('reopening closed branch head %s\n') % dest)
345 345
346 346 def _performrebase(self, tr):
347 347 repo, ui, opts = self.repo, self.ui, self.opts
348 348 if self.keepbranchesf:
349 349 # insert _savebranch at the start of extrafns so if
350 350 # there's a user-provided extrafn it can clobber branch if
351 351 # desired
352 352 self.extrafns.insert(0, _savebranch)
353 353 if self.collapsef:
354 354 branches = set()
355 355 for rev in self.state:
356 356 branches.add(repo[rev].branch())
357 357 if len(branches) > 1:
358 358 raise error.Abort(_('cannot collapse multiple named '
359 359 'branches'))
360 360
361 361 # Rebase
362 362 if not self.targetancestors:
363 363 self.targetancestors = repo.changelog.ancestors([self.target],
364 364 inclusive=True)
365 365
366 366 # Keep track of the current bookmarks in order to reset them later
367 367 self.currentbookmarks = repo._bookmarks.copy()
368 368 self.activebookmark = self.activebookmark or repo._activebookmark
369 369 if self.activebookmark:
370 370 bookmarks.deactivate(repo)
371 371
372 372 # Store the state before we begin so users can run 'hg rebase --abort'
373 373 # if we fail before the transaction closes.
374 374 self.storestatus()
375 375
376 376 sortedrevs = repo.revs('sort(%ld, -topo)', self.state)
377 377 cands = [k for k, v in self.state.iteritems() if v == revtodo]
378 378 total = len(cands)
379 379 pos = 0
380 380 for rev in sortedrevs:
381 381 ctx = repo[rev]
382 382 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
383 383 ctx.description().split('\n', 1)[0])
384 384 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
385 385 if names:
386 386 desc += ' (%s)' % ' '.join(names)
387 387 if self.state[rev] == revtodo:
388 388 pos += 1
389 389 ui.status(_('rebasing %s\n') % desc)
390 390 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
391 391 _('changesets'), total)
392 392 p1, p2, base = defineparents(repo, rev, self.target,
393 393 self.state,
394 394 self.targetancestors,
395 395 self.obsoletenotrebased)
396 396 self.storestatus(tr=tr)
397 397 storecollapsemsg(repo, self.collapsemsg)
398 398 if len(repo[None].parents()) == 2:
399 399 repo.ui.debug('resuming interrupted rebase\n')
400 400 else:
401 401 try:
402 402 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
403 403 'rebase')
404 404 stats = rebasenode(repo, rev, p1, base, self.state,
405 405 self.collapsef, self.target)
406 406 if stats and stats[3] > 0:
407 407 raise error.InterventionRequired(
408 408 _('unresolved conflicts (see hg '
409 409 'resolve, then hg rebase --continue)'))
410 410 finally:
411 411 ui.setconfig('ui', 'forcemerge', '', 'rebase')
412 412 if not self.collapsef:
413 413 merging = p2 != nullrev
414 414 editform = cmdutil.mergeeditform(merging, 'rebase')
415 415 editor = cmdutil.getcommiteditor(editform=editform, **opts)
416 416 newnode = concludenode(repo, rev, p1, p2,
417 417 extrafn=_makeextrafn(self.extrafns),
418 418 editor=editor,
419 419 keepbranches=self.keepbranchesf,
420 420 date=self.date)
421 421 else:
422 422 # Skip commit if we are collapsing
423 423 repo.dirstate.beginparentchange()
424 424 repo.setparents(repo[p1].node())
425 425 repo.dirstate.endparentchange()
426 426 newnode = None
427 427 # Update the state
428 428 if newnode is not None:
429 429 self.state[rev] = repo[newnode].rev()
430 430 ui.debug('rebased as %s\n' % short(newnode))
431 431 else:
432 432 if not self.collapsef:
433 433 ui.warn(_('note: rebase of %d:%s created no changes '
434 434 'to commit\n') % (rev, ctx))
435 435 self.skipped.add(rev)
436 436 self.state[rev] = p1
437 437 ui.debug('next revision set to %s\n' % p1)
438 438 elif self.state[rev] == nullmerge:
439 439 ui.debug('ignoring null merge rebase of %s\n' % rev)
440 440 elif self.state[rev] == revignored:
441 441 ui.status(_('not rebasing ignored %s\n') % desc)
442 442 elif self.state[rev] == revprecursor:
443 443 targetctx = repo[self.obsoletenotrebased[rev]]
444 444 desctarget = '%d:%s "%s"' % (targetctx.rev(), targetctx,
445 445 targetctx.description().split('\n', 1)[0])
446 446 msg = _('note: not rebasing %s, already in destination as %s\n')
447 447 ui.status(msg % (desc, desctarget))
448 448 elif self.state[rev] == revpruned:
449 449 msg = _('note: not rebasing %s, it has no successor\n')
450 450 ui.status(msg % desc)
451 451 else:
452 452 ui.status(_('already rebased %s as %s\n') %
453 453 (desc, repo[self.state[rev]]))
454 454
455 455 ui.progress(_('rebasing'), None)
456 456 ui.note(_('rebase merging completed\n'))
457 457
458 458 def _finishrebase(self):
459 459 repo, ui, opts = self.repo, self.ui, self.opts
460 460 if self.collapsef and not self.keepopen:
461 461 p1, p2, _base = defineparents(repo, min(self.state),
462 462 self.target, self.state,
463 463 self.targetancestors,
464 464 self.obsoletenotrebased)
465 465 editopt = opts.get('edit')
466 466 editform = 'rebase.collapse'
467 467 if self.collapsemsg:
468 468 commitmsg = self.collapsemsg
469 469 else:
470 470 commitmsg = 'Collapsed revision'
471 471 for rebased in self.state:
472 472 if rebased not in self.skipped and\
473 473 self.state[rebased] > nullmerge:
474 474 commitmsg += '\n* %s' % repo[rebased].description()
475 475 editopt = True
476 476 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
477 477 revtoreuse = max(self.state)
478 478 newnode = concludenode(repo, revtoreuse, p1, self.external,
479 479 commitmsg=commitmsg,
480 480 extrafn=_makeextrafn(self.extrafns),
481 481 editor=editor,
482 482 keepbranches=self.keepbranchesf,
483 483 date=self.date)
484 484 if newnode is None:
485 485 newrev = self.target
486 486 else:
487 487 newrev = repo[newnode].rev()
488 488 for oldrev in self.state.iterkeys():
489 489 if self.state[oldrev] > nullmerge:
490 490 self.state[oldrev] = newrev
491 491
492 492 if 'qtip' in repo.tags():
493 493 updatemq(repo, self.state, self.skipped, **opts)
494 494
495 495 if self.currentbookmarks:
496 496 # Nodeids are needed to reset bookmarks
497 497 nstate = {}
498 498 for k, v in self.state.iteritems():
499 499 if v > nullmerge:
500 500 nstate[repo[k].node()] = repo[v].node()
501 501 elif v == revprecursor:
502 502 succ = self.obsoletenotrebased[k]
503 503 nstate[repo[k].node()] = repo[succ].node()
504 504 # XXX this is the same as dest.node() for the non-continue path --
505 505 # this should probably be cleaned up
506 506 targetnode = repo[self.target].node()
507 507
508 508 # restore original working directory
509 509 # (we do this before stripping)
510 510 newwd = self.state.get(self.originalwd, self.originalwd)
511 511 if newwd == revprecursor:
512 512 newwd = self.obsoletenotrebased[self.originalwd]
513 513 elif newwd < 0:
514 514 # original directory is a parent of rebase set root or ignored
515 515 newwd = self.originalwd
516 516 if newwd not in [c.rev() for c in repo[None].parents()]:
517 517 ui.note(_("update back to initial working directory parent\n"))
518 518 hg.updaterepo(repo, newwd, False)
519 519
520 520 if self.currentbookmarks:
521 521 with repo.transaction('bookmark') as tr:
522 522 updatebookmarks(repo, targetnode, nstate,
523 523 self.currentbookmarks, tr)
524 524 if self.activebookmark not in repo._bookmarks:
525 525 # active bookmark was divergent one and has been deleted
526 526 self.activebookmark = None
527 527
528 528 if not self.keepf:
529 529 collapsedas = None
530 530 if self.collapsef:
531 531 collapsedas = newnode
532 532 clearrebased(ui, repo, self.state, self.skipped, collapsedas)
533 533
534 534 clearstatus(repo)
535 535 clearcollapsemsg(repo)
536 536
537 537 ui.note(_("rebase completed\n"))
538 538 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
539 539 if self.skipped:
540 540 skippedlen = len(self.skipped)
541 541 ui.note(_("%d revisions have been skipped\n") % skippedlen)
542 542
543 543 if (self.activebookmark and
544 544 repo['.'].node() == repo._bookmarks[self.activebookmark]):
545 545 bookmarks.activate(repo, self.activebookmark)
546 546
547 547 @command('rebase',
548 548 [('s', 'source', '',
549 549 _('rebase the specified changeset and descendants'), _('REV')),
550 550 ('b', 'base', '',
551 551 _('rebase everything from branching point of specified changeset'),
552 552 _('REV')),
553 553 ('r', 'rev', [],
554 554 _('rebase these revisions'),
555 555 _('REV')),
556 556 ('d', 'dest', '',
557 557 _('rebase onto the specified changeset'), _('REV')),
558 558 ('', 'collapse', False, _('collapse the rebased changesets')),
559 559 ('m', 'message', '',
560 560 _('use text as collapse commit message'), _('TEXT')),
561 561 ('e', 'edit', False, _('invoke editor on commit messages')),
562 562 ('l', 'logfile', '',
563 563 _('read collapse commit message from file'), _('FILE')),
564 564 ('k', 'keep', False, _('keep original changesets')),
565 565 ('', 'keepbranches', False, _('keep original branch names')),
566 566 ('D', 'detach', False, _('(DEPRECATED)')),
567 567 ('i', 'interactive', False, _('(DEPRECATED)')),
568 568 ('t', 'tool', '', _('specify merge tool')),
569 569 ('c', 'continue', False, _('continue an interrupted rebase')),
570 570 ('a', 'abort', False, _('abort an interrupted rebase'))] +
571 571 templateopts,
572 572 _('[-s REV | -b REV] [-d REV] [OPTION]'))
573 573 def rebase(ui, repo, **opts):
574 574 """move changeset (and descendants) to a different branch
575 575
576 576 Rebase uses repeated merging to graft changesets from one part of
577 577 history (the source) onto another (the destination). This can be
578 578 useful for linearizing *local* changes relative to a master
579 579 development tree.
580 580
581 581 Published commits cannot be rebased (see :hg:`help phases`).
582 582 To copy commits, see :hg:`help graft`.
583 583
584 584 If you don't specify a destination changeset (``-d/--dest``), rebase
585 585 will use the same logic as :hg:`merge` to pick a destination. if
586 586 the current branch contains exactly one other head, the other head
587 587 is merged with by default. Otherwise, an explicit revision with
588 588 which to merge with must be provided. (destination changeset is not
589 589 modified by rebasing, but new changesets are added as its
590 590 descendants.)
591 591
592 592 Here are the ways to select changesets:
593 593
594 594 1. Explicitly select them using ``--rev``.
595 595
596 596 2. Use ``--source`` to select a root changeset and include all of its
597 597 descendants.
598 598
599 599 3. Use ``--base`` to select a changeset; rebase will find ancestors
600 600 and their descendants which are not also ancestors of the destination.
601 601
602 602 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
603 603 rebase will use ``--base .`` as above.
604 604
605 605 Rebase will destroy original changesets unless you use ``--keep``.
606 606 It will also move your bookmarks (even if you do).
607 607
608 608 Some changesets may be dropped if they do not contribute changes
609 609 (e.g. merges from the destination branch).
610 610
611 611 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
612 612 a named branch with two heads. You will need to explicitly specify source
613 613 and/or destination.
614 614
615 615 If you need to use a tool to automate merge/conflict decisions, you
616 616 can specify one with ``--tool``, see :hg:`help merge-tools`.
617 617 As a caveat: the tool will not be used to mediate when a file was
618 618 deleted, there is no hook presently available for this.
619 619
620 620 If a rebase is interrupted to manually resolve a conflict, it can be
621 621 continued with --continue/-c or aborted with --abort/-a.
622 622
623 623 .. container:: verbose
624 624
625 625 Examples:
626 626
627 627 - move "local changes" (current commit back to branching point)
628 628 to the current branch tip after a pull::
629 629
630 630 hg rebase
631 631
632 632 - move a single changeset to the stable branch::
633 633
634 634 hg rebase -r 5f493448 -d stable
635 635
636 636 - splice a commit and all its descendants onto another part of history::
637 637
638 638 hg rebase --source c0c3 --dest 4cf9
639 639
640 640 - rebase everything on a branch marked by a bookmark onto the
641 641 default branch::
642 642
643 643 hg rebase --base myfeature --dest default
644 644
645 645 - collapse a sequence of changes into a single commit::
646 646
647 647 hg rebase --collapse -r 1520:1525 -d .
648 648
649 649 - move a named branch while preserving its name::
650 650
651 651 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
652 652
653 653 Returns 0 on success, 1 if nothing to rebase or there are
654 654 unresolved conflicts.
655 655
656 656 """
657 657 rbsrt = rebaseruntime(repo, ui, opts)
658 658
659 659 lock = wlock = None
660 660 try:
661 661 wlock = repo.wlock()
662 662 lock = repo.lock()
663 663
664 664 # Validate input and define rebasing points
665 665 destf = opts.get('dest', None)
666 666 srcf = opts.get('source', None)
667 667 basef = opts.get('base', None)
668 668 revf = opts.get('rev', [])
669 669 # search default destination in this space
670 670 # used in the 'hg pull --rebase' case, see issue 5214.
671 671 destspace = opts.get('_destspace')
672 672 contf = opts.get('continue')
673 673 abortf = opts.get('abort')
674 674 if opts.get('interactive'):
675 675 try:
676 676 if extensions.find('histedit'):
677 677 enablehistedit = ''
678 678 except KeyError:
679 679 enablehistedit = " --config extensions.histedit="
680 680 help = "hg%s help -e histedit" % enablehistedit
681 681 msg = _("interactive history editing is supported by the "
682 682 "'histedit' extension (see \"%s\")") % help
683 683 raise error.Abort(msg)
684 684
685 685 if rbsrt.collapsemsg and not rbsrt.collapsef:
686 686 raise error.Abort(
687 687 _('message can only be specified with collapse'))
688 688
689 689 if contf or abortf:
690 690 if contf and abortf:
691 691 raise error.Abort(_('cannot use both abort and continue'))
692 692 if rbsrt.collapsef:
693 693 raise error.Abort(
694 694 _('cannot use collapse with continue or abort'))
695 695 if srcf or basef or destf:
696 696 raise error.Abort(
697 697 _('abort and continue do not allow specifying revisions'))
698 698 if abortf and opts.get('tool', False):
699 699 ui.warn(_('tool option will be ignored\n'))
700 700 if contf:
701 701 ms = mergemod.mergestate.read(repo)
702 702 mergeutil.checkunresolved(ms)
703 703
704 704 retcode = rbsrt._prepareabortorcontinue(abortf)
705 705 if retcode is not None:
706 706 return retcode
707 707 else:
708 708 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
709 709 destspace=destspace)
710 710 retcode = rbsrt._preparenewrebase(dest, rebaseset)
711 711 if retcode is not None:
712 712 return retcode
713 713
714 714 with repo.transaction('rebase') as tr:
715 715 try:
716 716 rbsrt._performrebase(tr)
717 717 except error.InterventionRequired:
718 718 tr.close()
719 719 raise
720 720 rbsrt._finishrebase()
721 721 finally:
722 722 release(lock, wlock)
723 723
724 724 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=[],
725 725 destspace=None):
726 726 """use revisions argument to define destination and rebase set
727 727 """
728 728 # destspace is here to work around issues with `hg pull --rebase` see
729 729 # issue5214 for details
730 730 if srcf and basef:
731 731 raise error.Abort(_('cannot specify both a source and a base'))
732 732 if revf and basef:
733 733 raise error.Abort(_('cannot specify both a revision and a base'))
734 734 if revf and srcf:
735 735 raise error.Abort(_('cannot specify both a revision and a source'))
736 736
737 737 cmdutil.checkunfinished(repo)
738 738 cmdutil.bailifchanged(repo)
739 739
740 740 if destf:
741 741 dest = scmutil.revsingle(repo, destf)
742 742
743 743 if revf:
744 744 rebaseset = scmutil.revrange(repo, revf)
745 745 if not rebaseset:
746 746 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
747 747 return None, None
748 748 elif srcf:
749 749 src = scmutil.revrange(repo, [srcf])
750 750 if not src:
751 751 ui.status(_('empty "source" revision set - nothing to rebase\n'))
752 752 return None, None
753 753 rebaseset = repo.revs('(%ld)::', src)
754 754 assert rebaseset
755 755 else:
756 756 base = scmutil.revrange(repo, [basef or '.'])
757 757 if not base:
758 758 ui.status(_('empty "base" revision set - '
759 759 "can't compute rebase set\n"))
760 760 return None, None
761 761 if not destf:
762 762 dest = repo[_destrebase(repo, base, destspace=destspace)]
763 763 destf = str(dest)
764 764
765 765 roots = [] # selected children of branching points
766 766 bpbase = {} # {branchingpoint: [origbase]}
767 767 for b in base: # group bases by branching points
768 768 bp = repo.revs('ancestor(%d, %d)', b, dest).first()
769 769 bpbase[bp] = bpbase.get(bp, []) + [b]
770 770 if None in bpbase:
771 771 # emulate the old behavior, showing "nothing to rebase" (a better
772 772 # behavior may be abort with "cannot find branching point" error)
773 773 bpbase.clear()
774 774 for bp, bs in bpbase.iteritems(): # calculate roots
775 775 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
776 776
777 777 rebaseset = repo.revs('%ld::', roots)
778 778
779 779 if not rebaseset:
780 780 # transform to list because smartsets are not comparable to
781 781 # lists. This should be improved to honor laziness of
782 782 # smartset.
783 783 if list(base) == [dest.rev()]:
784 784 if basef:
785 785 ui.status(_('nothing to rebase - %s is both "base"'
786 786 ' and destination\n') % dest)
787 787 else:
788 788 ui.status(_('nothing to rebase - working directory '
789 789 'parent is also destination\n'))
790 790 elif not repo.revs('%ld - ::%d', base, dest):
791 791 if basef:
792 792 ui.status(_('nothing to rebase - "base" %s is '
793 793 'already an ancestor of destination '
794 794 '%s\n') %
795 795 ('+'.join(str(repo[r]) for r in base),
796 796 dest))
797 797 else:
798 798 ui.status(_('nothing to rebase - working '
799 799 'directory parent is already an '
800 800 'ancestor of destination %s\n') % dest)
801 801 else: # can it happen?
802 802 ui.status(_('nothing to rebase from %s to %s\n') %
803 803 ('+'.join(str(repo[r]) for r in base), dest))
804 804 return None, None
805 805
806 806 if not destf:
807 807 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
808 808 destf = str(dest)
809 809
810 810 return dest, rebaseset
811 811
812 812 def externalparent(repo, state, targetancestors):
813 813 """Return the revision that should be used as the second parent
814 814 when the revisions in state is collapsed on top of targetancestors.
815 815 Abort if there is more than one parent.
816 816 """
817 817 parents = set()
818 818 source = min(state)
819 819 for rev in state:
820 820 if rev == source:
821 821 continue
822 822 for p in repo[rev].parents():
823 823 if (p.rev() not in state
824 824 and p.rev() not in targetancestors):
825 825 parents.add(p.rev())
826 826 if not parents:
827 827 return nullrev
828 828 if len(parents) == 1:
829 829 return parents.pop()
830 830 raise error.Abort(_('unable to collapse on top of %s, there is more '
831 831 'than one external parent: %s') %
832 832 (max(targetancestors),
833 833 ', '.join(str(p) for p in sorted(parents))))
834 834
835 835 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
836 836 keepbranches=False, date=None):
837 837 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
838 838 but also store useful information in extra.
839 839 Return node of committed revision.'''
840 840 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
841 841 try:
842 842 repo.setparents(repo[p1].node(), repo[p2].node())
843 843 ctx = repo[rev]
844 844 if commitmsg is None:
845 845 commitmsg = ctx.description()
846 846 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
847 847 extra = {'rebase_source': ctx.hex()}
848 848 if extrafn:
849 849 extrafn(ctx, extra)
850 850
851 851 backup = repo.ui.backupconfig('phases', 'new-commit')
852 852 try:
853 853 targetphase = max(ctx.phase(), phases.draft)
854 854 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
855 855 if keepbranch:
856 856 repo.ui.setconfig('ui', 'allowemptycommit', True)
857 857 # Commit might fail if unresolved files exist
858 858 if date is None:
859 859 date = ctx.date()
860 860 newnode = repo.commit(text=commitmsg, user=ctx.user(),
861 861 date=date, extra=extra, editor=editor)
862 862 finally:
863 863 repo.ui.restoreconfig(backup)
864 864
865 865 repo.dirstate.setbranch(repo[newnode].branch())
866 866 dsguard.close()
867 867 return newnode
868 868 finally:
869 869 release(dsguard)
870 870
871 871 def rebasenode(repo, rev, p1, base, state, collapse, target):
872 872 'Rebase a single revision rev on top of p1 using base as merge ancestor'
873 873 # Merge phase
874 874 # Update to target and merge it with local
875 875 if repo['.'].rev() != p1:
876 876 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
877 877 mergemod.update(repo, p1, False, True)
878 878 else:
879 879 repo.ui.debug(" already in target\n")
880 880 repo.dirstate.write(repo.currenttransaction())
881 881 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
882 882 if base is not None:
883 883 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
884 884 # When collapsing in-place, the parent is the common ancestor, we
885 885 # have to allow merging with it.
886 886 stats = mergemod.update(repo, rev, True, True, base, collapse,
887 887 labels=['dest', 'source'])
888 888 if collapse:
889 889 copies.duplicatecopies(repo, rev, target)
890 890 else:
891 891 # If we're not using --collapse, we need to
892 892 # duplicate copies between the revision we're
893 893 # rebasing and its first parent, but *not*
894 894 # duplicate any copies that have already been
895 895 # performed in the destination.
896 896 p1rev = repo[rev].p1().rev()
897 897 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
898 898 return stats
899 899
900 900 def nearestrebased(repo, rev, state):
901 901 """return the nearest ancestors of rev in the rebase result"""
902 902 rebased = [r for r in state if state[r] > nullmerge]
903 903 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
904 904 if candidates:
905 905 return state[candidates.first()]
906 906 else:
907 907 return None
908 908
909 909 def _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs, rebaseobsskipped):
910 910 """
911 911 Abort if rebase will create divergence or rebase is noop because of markers
912 912
913 913 `rebaseobsrevs`: set of obsolete revision in source
914 914 `rebasesetrevs`: set of revisions to be rebased from source
915 915 `rebaseobsskipped`: set of revisions from source skipped because they have
916 916 successors in destination
917 917 """
918 918 # Obsolete node with successors not in dest leads to divergence
919 919 divergenceok = ui.configbool('experimental',
920 920 'allowdivergence')
921 921 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
922 922
923 923 if divergencebasecandidates and not divergenceok:
924 924 divhashes = (str(repo[r])
925 925 for r in divergencebasecandidates)
926 926 msg = _("this rebase will cause "
927 927 "divergences from: %s")
928 928 h = _("to force the rebase please set "
929 929 "experimental.allowdivergence=True")
930 930 raise error.Abort(msg % (",".join(divhashes),), hint=h)
931 931
932 932 def defineparents(repo, rev, target, state, targetancestors,
933 933 obsoletenotrebased):
934 934 'Return the new parent relationship of the revision that will be rebased'
935 935 parents = repo[rev].parents()
936 936 p1 = p2 = nullrev
937 937 rp1 = None
938 938
939 939 p1n = parents[0].rev()
940 940 if p1n in targetancestors:
941 941 p1 = target
942 942 elif p1n in state:
943 943 if state[p1n] == nullmerge:
944 944 p1 = target
945 945 elif state[p1n] in revskipped:
946 946 p1 = nearestrebased(repo, p1n, state)
947 947 if p1 is None:
948 948 p1 = target
949 949 else:
950 950 p1 = state[p1n]
951 951 else: # p1n external
952 952 p1 = target
953 953 p2 = p1n
954 954
955 955 if len(parents) == 2 and parents[1].rev() not in targetancestors:
956 956 p2n = parents[1].rev()
957 957 # interesting second parent
958 958 if p2n in state:
959 959 if p1 == target: # p1n in targetancestors or external
960 960 p1 = state[p2n]
961 961 if p1 == revprecursor:
962 962 rp1 = obsoletenotrebased[p2n]
963 963 elif state[p2n] in revskipped:
964 964 p2 = nearestrebased(repo, p2n, state)
965 965 if p2 is None:
966 966 # no ancestors rebased yet, detach
967 967 p2 = target
968 968 else:
969 969 p2 = state[p2n]
970 970 else: # p2n external
971 971 if p2 != nullrev: # p1n external too => rev is a merged revision
972 972 raise error.Abort(_('cannot use revision %d as base, result '
973 973 'would have 3 parents') % rev)
974 974 p2 = p2n
975 975 repo.ui.debug(" future parents are %d and %d\n" %
976 976 (repo[rp1 or p1].rev(), repo[p2].rev()))
977 977
978 978 if not any(p.rev() in state for p in parents):
979 979 # Case (1) root changeset of a non-detaching rebase set.
980 980 # Let the merge mechanism find the base itself.
981 981 base = None
982 982 elif not repo[rev].p2():
983 983 # Case (2) detaching the node with a single parent, use this parent
984 984 base = repo[rev].p1().rev()
985 985 else:
986 986 # Assuming there is a p1, this is the case where there also is a p2.
987 987 # We are thus rebasing a merge and need to pick the right merge base.
988 988 #
989 989 # Imagine we have:
990 990 # - M: current rebase revision in this step
991 991 # - A: one parent of M
992 992 # - B: other parent of M
993 993 # - D: destination of this merge step (p1 var)
994 994 #
995 995 # Consider the case where D is a descendant of A or B and the other is
996 996 # 'outside'. In this case, the right merge base is the D ancestor.
997 997 #
998 998 # An informal proof, assuming A is 'outside' and B is the D ancestor:
999 999 #
1000 1000 # If we pick B as the base, the merge involves:
1001 1001 # - changes from B to M (actual changeset payload)
1002 1002 # - changes from B to D (induced by rebase) as D is a rebased
1003 1003 # version of B)
1004 1004 # Which exactly represent the rebase operation.
1005 1005 #
1006 1006 # If we pick A as the base, the merge involves:
1007 1007 # - changes from A to M (actual changeset payload)
1008 1008 # - changes from A to D (with include changes between unrelated A and B
1009 1009 # plus changes induced by rebase)
1010 1010 # Which does not represent anything sensible and creates a lot of
1011 1011 # conflicts. A is thus not the right choice - B is.
1012 1012 #
1013 1013 # Note: The base found in this 'proof' is only correct in the specified
1014 1014 # case. This base does not make sense if is not D a descendant of A or B
1015 1015 # or if the other is not parent 'outside' (especially not if the other
1016 1016 # parent has been rebased). The current implementation does not
1017 1017 # make it feasible to consider different cases separately. In these
1018 1018 # other cases we currently just leave it to the user to correctly
1019 1019 # resolve an impossible merge using a wrong ancestor.
1020 1020 #
1021 1021 # xx, p1 could be -4, and both parents could probably be -4...
1022 1022 for p in repo[rev].parents():
1023 1023 if state.get(p.rev()) == p1:
1024 1024 base = p.rev()
1025 1025 break
1026 1026 else: # fallback when base not found
1027 1027 base = None
1028 1028
1029 1029 # Raise because this function is called wrong (see issue 4106)
1030 1030 raise AssertionError('no base found to rebase on '
1031 1031 '(defineparents called wrong)')
1032 1032 return rp1 or p1, p2, base
1033 1033
1034 1034 def isagitpatch(repo, patchname):
1035 1035 'Return true if the given patch is in git format'
1036 1036 mqpatch = os.path.join(repo.mq.path, patchname)
1037 1037 for line in patch.linereader(file(mqpatch, 'rb')):
1038 1038 if line.startswith('diff --git'):
1039 1039 return True
1040 1040 return False
1041 1041
1042 1042 def updatemq(repo, state, skipped, **opts):
1043 1043 'Update rebased mq patches - finalize and then import them'
1044 1044 mqrebase = {}
1045 1045 mq = repo.mq
1046 1046 original_series = mq.fullseries[:]
1047 1047 skippedpatches = set()
1048 1048
1049 1049 for p in mq.applied:
1050 1050 rev = repo[p.node].rev()
1051 1051 if rev in state:
1052 1052 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1053 1053 (rev, p.name))
1054 1054 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1055 1055 else:
1056 1056 # Applied but not rebased, not sure this should happen
1057 1057 skippedpatches.add(p.name)
1058 1058
1059 1059 if mqrebase:
1060 1060 mq.finish(repo, mqrebase.keys())
1061 1061
1062 1062 # We must start import from the newest revision
1063 1063 for rev in sorted(mqrebase, reverse=True):
1064 1064 if rev not in skipped:
1065 1065 name, isgit = mqrebase[rev]
1066 1066 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1067 1067 (name, state[rev], repo[state[rev]]))
1068 1068 mq.qimport(repo, (), patchname=name, git=isgit,
1069 1069 rev=[str(state[rev])])
1070 1070 else:
1071 1071 # Rebased and skipped
1072 1072 skippedpatches.add(mqrebase[rev][0])
1073 1073
1074 1074 # Patches were either applied and rebased and imported in
1075 1075 # order, applied and removed or unapplied. Discard the removed
1076 1076 # ones while preserving the original series order and guards.
1077 1077 newseries = [s for s in original_series
1078 1078 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1079 1079 mq.fullseries[:] = newseries
1080 1080 mq.seriesdirty = True
1081 1081 mq.savedirty()
1082 1082
1083 1083 def updatebookmarks(repo, targetnode, nstate, originalbookmarks, tr):
1084 1084 'Move bookmarks to their correct changesets, and delete divergent ones'
1085 1085 marks = repo._bookmarks
1086 1086 for k, v in originalbookmarks.iteritems():
1087 1087 if v in nstate:
1088 1088 # update the bookmarks for revs that have moved
1089 1089 marks[k] = nstate[v]
1090 1090 bookmarks.deletedivergent(repo, [targetnode], k)
1091 1091 marks.recordchange(tr)
1092 1092
1093 1093 def storecollapsemsg(repo, collapsemsg):
1094 1094 'Store the collapse message to allow recovery'
1095 1095 collapsemsg = collapsemsg or ''
1096 1096 f = repo.vfs("last-message.txt", "w")
1097 1097 f.write("%s\n" % collapsemsg)
1098 1098 f.close()
1099 1099
1100 1100 def clearcollapsemsg(repo):
1101 1101 'Remove collapse message file'
1102 1102 util.unlinkpath(repo.join("last-message.txt"), ignoremissing=True)
1103 1103
1104 1104 def restorecollapsemsg(repo, isabort):
1105 1105 'Restore previously stored collapse message'
1106 1106 try:
1107 1107 f = repo.vfs("last-message.txt")
1108 1108 collapsemsg = f.readline().strip()
1109 1109 f.close()
1110 1110 except IOError as err:
1111 1111 if err.errno != errno.ENOENT:
1112 1112 raise
1113 1113 if isabort:
1114 1114 # Oh well, just abort like normal
1115 1115 collapsemsg = ''
1116 1116 else:
1117 1117 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1118 1118 return collapsemsg
1119 1119
1120 1120 def clearstatus(repo):
1121 1121 'Remove the status files'
1122 1122 _clearrebasesetvisibiliy(repo)
1123 1123 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
1124 1124
1125 1125 def needupdate(repo, state):
1126 1126 '''check whether we should `update --clean` away from a merge, or if
1127 1127 somehow the working dir got forcibly updated, e.g. by older hg'''
1128 1128 parents = [p.rev() for p in repo[None].parents()]
1129 1129
1130 1130 # Are we in a merge state at all?
1131 1131 if len(parents) < 2:
1132 1132 return False
1133 1133
1134 1134 # We should be standing on the first as-of-yet unrebased commit.
1135 1135 firstunrebased = min([old for old, new in state.iteritems()
1136 1136 if new == nullrev])
1137 1137 if firstunrebased in parents:
1138 1138 return True
1139 1139
1140 1140 return False
1141 1141
1142 1142 def abort(repo, originalwd, target, state, activebookmark=None):
1143 1143 '''Restore the repository to its original state. Additional args:
1144 1144
1145 1145 activebookmark: the name of the bookmark that should be active after the
1146 1146 restore'''
1147 1147
1148 1148 try:
1149 1149 # If the first commits in the rebased set get skipped during the rebase,
1150 1150 # their values within the state mapping will be the target rev id. The
1151 1151 # dstates list must must not contain the target rev (issue4896)
1152 1152 dstates = [s for s in state.values() if s >= 0 and s != target]
1153 1153 immutable = [d for d in dstates if not repo[d].mutable()]
1154 1154 cleanup = True
1155 1155 if immutable:
1156 1156 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1157 1157 % ', '.join(str(repo[r]) for r in immutable),
1158 1158 hint=_("see 'hg help phases' for details"))
1159 1159 cleanup = False
1160 1160
1161 1161 descendants = set()
1162 1162 if dstates:
1163 1163 descendants = set(repo.changelog.descendants(dstates))
1164 1164 if descendants - set(dstates):
1165 1165 repo.ui.warn(_("warning: new changesets detected on target branch, "
1166 1166 "can't strip\n"))
1167 1167 cleanup = False
1168 1168
1169 1169 if cleanup:
1170 1170 shouldupdate = False
1171 1171 rebased = filter(lambda x: x >= 0 and x != target, state.values())
1172 1172 if rebased:
1173 1173 strippoints = [
1174 1174 c.node() for c in repo.set('roots(%ld)', rebased)]
1175 1175
1176 1176 updateifonnodes = set(rebased)
1177 1177 updateifonnodes.add(target)
1178 1178 updateifonnodes.add(originalwd)
1179 1179 shouldupdate = repo['.'].rev() in updateifonnodes
1180 1180
1181 1181 # Update away from the rebase if necessary
1182 1182 if shouldupdate or needupdate(repo, state):
1183 1183 mergemod.update(repo, originalwd, False, True)
1184 1184
1185 1185 # Strip from the first rebased revision
1186 1186 if rebased:
1187 1187 # no backup of rebased cset versions needed
1188 1188 repair.strip(repo.ui, repo, strippoints)
1189 1189
1190 1190 if activebookmark and activebookmark in repo._bookmarks:
1191 1191 bookmarks.activate(repo, activebookmark)
1192 1192
1193 1193 finally:
1194 1194 clearstatus(repo)
1195 1195 clearcollapsemsg(repo)
1196 1196 repo.ui.warn(_('rebase aborted\n'))
1197 1197 return 0
1198 1198
1199 1199 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1200 1200 '''Define which revisions are going to be rebased and where
1201 1201
1202 1202 repo: repo
1203 1203 dest: context
1204 1204 rebaseset: set of rev
1205 1205 '''
1206 1206 originalwd = repo['.'].rev()
1207 1207 _setrebasesetvisibility(repo, set(rebaseset) | set([originalwd]))
1208 1208
1209 1209 # This check isn't strictly necessary, since mq detects commits over an
1210 1210 # applied patch. But it prevents messing up the working directory when
1211 1211 # a partially completed rebase is blocked by mq.
1212 1212 if 'qtip' in repo.tags() and (dest.node() in
1213 1213 [s.node for s in repo.mq.applied]):
1214 1214 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1215 1215
1216 1216 roots = list(repo.set('roots(%ld)', rebaseset))
1217 1217 if not roots:
1218 1218 raise error.Abort(_('no matching revisions'))
1219 1219 roots.sort()
1220 1220 state = {}
1221 1221 detachset = set()
1222 1222 for root in roots:
1223 1223 commonbase = root.ancestor(dest)
1224 1224 if commonbase == root:
1225 1225 raise error.Abort(_('source is ancestor of destination'))
1226 1226 if commonbase == dest:
1227 1227 samebranch = root.branch() == dest.branch()
1228 1228 if not collapse and samebranch and root in dest.children():
1229 1229 repo.ui.debug('source is a child of destination\n')
1230 1230 return None
1231 1231
1232 1232 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1233 1233 state.update(dict.fromkeys(rebaseset, revtodo))
1234 1234 # Rebase tries to turn <dest> into a parent of <root> while
1235 1235 # preserving the number of parents of rebased changesets:
1236 1236 #
1237 1237 # - A changeset with a single parent will always be rebased as a
1238 1238 # changeset with a single parent.
1239 1239 #
1240 1240 # - A merge will be rebased as merge unless its parents are both
1241 1241 # ancestors of <dest> or are themselves in the rebased set and
1242 1242 # pruned while rebased.
1243 1243 #
1244 1244 # If one parent of <root> is an ancestor of <dest>, the rebased
1245 1245 # version of this parent will be <dest>. This is always true with
1246 1246 # --base option.
1247 1247 #
1248 1248 # Otherwise, we need to *replace* the original parents with
1249 1249 # <dest>. This "detaches" the rebased set from its former location
1250 1250 # and rebases it onto <dest>. Changes introduced by ancestors of
1251 1251 # <root> not common with <dest> (the detachset, marked as
1252 1252 # nullmerge) are "removed" from the rebased changesets.
1253 1253 #
1254 1254 # - If <root> has a single parent, set it to <dest>.
1255 1255 #
1256 1256 # - If <root> is a merge, we cannot decide which parent to
1257 1257 # replace, the rebase operation is not clearly defined.
1258 1258 #
1259 1259 # The table below sums up this behavior:
1260 1260 #
1261 1261 # +------------------+----------------------+-------------------------+
1262 1262 # | | one parent | merge |
1263 1263 # +------------------+----------------------+-------------------------+
1264 1264 # | parent in | new parent is <dest> | parents in ::<dest> are |
1265 1265 # | ::<dest> | | remapped to <dest> |
1266 1266 # +------------------+----------------------+-------------------------+
1267 1267 # | unrelated source | new parent is <dest> | ambiguous, abort |
1268 1268 # +------------------+----------------------+-------------------------+
1269 1269 #
1270 1270 # The actual abort is handled by `defineparents`
1271 1271 if len(root.parents()) <= 1:
1272 1272 # ancestors of <root> not ancestors of <dest>
1273 1273 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1274 1274 [root.rev()]))
1275 1275 for r in detachset:
1276 1276 if r not in state:
1277 1277 state[r] = nullmerge
1278 1278 if len(roots) > 1:
1279 1279 # If we have multiple roots, we may have "hole" in the rebase set.
1280 1280 # Rebase roots that descend from those "hole" should not be detached as
1281 1281 # other root are. We use the special `revignored` to inform rebase that
1282 1282 # the revision should be ignored but that `defineparents` should search
1283 1283 # a rebase destination that make sense regarding rebased topology.
1284 1284 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1285 1285 for ignored in set(rebasedomain) - set(rebaseset):
1286 1286 state[ignored] = revignored
1287 1287 for r in obsoletenotrebased:
1288 1288 if obsoletenotrebased[r] is None:
1289 1289 state[r] = revpruned
1290 1290 else:
1291 1291 state[r] = revprecursor
1292 1292 return originalwd, dest.rev(), state
1293 1293
1294 1294 def clearrebased(ui, repo, state, skipped, collapsedas=None):
1295 1295 """dispose of rebased revision at the end of the rebase
1296 1296
1297 1297 If `collapsedas` is not None, the rebase was a collapse whose result if the
1298 1298 `collapsedas` node."""
1299 1299 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1300 1300 markers = []
1301 1301 for rev, newrev in sorted(state.items()):
1302 1302 if newrev >= 0:
1303 1303 if rev in skipped:
1304 1304 succs = ()
1305 1305 elif collapsedas is not None:
1306 1306 succs = (repo[collapsedas],)
1307 1307 else:
1308 1308 succs = (repo[newrev],)
1309 1309 markers.append((repo[rev], succs))
1310 1310 if markers:
1311 1311 obsolete.createmarkers(repo, markers)
1312 1312 else:
1313 1313 rebased = [rev for rev in state if state[rev] > nullmerge]
1314 1314 if rebased:
1315 1315 stripped = []
1316 1316 for root in repo.set('roots(%ld)', rebased):
1317 1317 if set(repo.changelog.descendants([root.rev()])) - set(state):
1318 1318 ui.warn(_("warning: new changesets detected "
1319 1319 "on source branch, not stripping\n"))
1320 1320 else:
1321 1321 stripped.append(root.node())
1322 1322 if stripped:
1323 1323 # backup the old csets by default
1324 1324 repair.strip(ui, repo, stripped, "all")
1325 1325
1326 1326
1327 1327 def pullrebase(orig, ui, repo, *args, **opts):
1328 1328 'Call rebase after pull if the latter has been invoked with --rebase'
1329 1329 ret = None
1330 1330 if opts.get('rebase'):
1331 1331 wlock = lock = None
1332 1332 try:
1333 1333 wlock = repo.wlock()
1334 1334 lock = repo.lock()
1335 1335 if opts.get('update'):
1336 1336 del opts['update']
1337 1337 ui.debug('--update and --rebase are not compatible, ignoring '
1338 1338 'the update flag\n')
1339 1339
1340 1340 cmdutil.checkunfinished(repo)
1341 1341 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1342 1342 'please commit or shelve your changes first'))
1343 1343
1344 1344 revsprepull = len(repo)
1345 1345 origpostincoming = commands.postincoming
1346 1346 def _dummy(*args, **kwargs):
1347 1347 pass
1348 1348 commands.postincoming = _dummy
1349 1349 try:
1350 1350 ret = orig(ui, repo, *args, **opts)
1351 1351 finally:
1352 1352 commands.postincoming = origpostincoming
1353 1353 revspostpull = len(repo)
1354 1354 if revspostpull > revsprepull:
1355 1355 # --rev option from pull conflict with rebase own --rev
1356 1356 # dropping it
1357 1357 if 'rev' in opts:
1358 1358 del opts['rev']
1359 1359 # positional argument from pull conflicts with rebase's own
1360 1360 # --source.
1361 1361 if 'source' in opts:
1362 1362 del opts['source']
1363 1363 # revsprepull is the len of the repo, not revnum of tip.
1364 1364 destspace = list(repo.changelog.revs(start=revsprepull))
1365 1365 opts['_destspace'] = destspace
1366 1366 try:
1367 1367 rebase(ui, repo, **opts)
1368 1368 except error.NoMergeDestAbort:
1369 1369 # we can maybe update instead
1370 1370 rev, _a, _b = destutil.destupdate(repo)
1371 1371 if rev == repo['.'].rev():
1372 1372 ui.status(_('nothing to rebase\n'))
1373 1373 else:
1374 1374 ui.status(_('nothing to rebase - updating instead\n'))
1375 1375 # not passing argument to get the bare update behavior
1376 1376 # with warning and trumpets
1377 1377 commands.update(ui, repo)
1378 1378 finally:
1379 1379 release(lock, wlock)
1380 1380 else:
1381 1381 if opts.get('tool'):
1382 1382 raise error.Abort(_('--tool can only be used with --rebase'))
1383 1383 ret = orig(ui, repo, *args, **opts)
1384 1384
1385 1385 return ret
1386 1386
1387 1387 def _setrebasesetvisibility(repo, revs):
1388 1388 """store the currently rebased set on the repo object
1389 1389
1390 1390 This is used by another function to prevent rebased revision to because
1391 1391 hidden (see issue4504)"""
1392 1392 repo = repo.unfiltered()
1393 1393 repo._rebaseset = revs
1394 1394 # invalidate cache if visibility changes
1395 1395 hiddens = repo.filteredrevcache.get('visible', set())
1396 1396 if revs & hiddens:
1397 1397 repo.invalidatevolatilesets()
1398 1398
1399 1399 def _clearrebasesetvisibiliy(repo):
1400 1400 """remove rebaseset data from the repo"""
1401 1401 repo = repo.unfiltered()
1402 1402 if '_rebaseset' in vars(repo):
1403 1403 del repo._rebaseset
1404 1404
1405 1405 def _rebasedvisible(orig, repo):
1406 1406 """ensure rebased revs stay visible (see issue4504)"""
1407 1407 blockers = orig(repo)
1408 1408 blockers.update(getattr(repo, '_rebaseset', ()))
1409 1409 return blockers
1410 1410
1411 1411 def _filterobsoleterevs(repo, revs):
1412 1412 """returns a set of the obsolete revisions in revs"""
1413 1413 return set(r for r in revs if repo[r].obsolete())
1414 1414
1415 1415 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1416 1416 """return a mapping obsolete => successor for all obsolete nodes to be
1417 1417 rebased that have a successors in the destination
1418 1418
1419 1419 obsolete => None entries in the mapping indicate nodes with no successor"""
1420 1420 obsoletenotrebased = {}
1421 1421
1422 1422 # Build a mapping successor => obsolete nodes for the obsolete
1423 1423 # nodes to be rebased
1424 1424 allsuccessors = {}
1425 1425 cl = repo.changelog
1426 1426 for r in rebaseobsrevs:
1427 1427 node = cl.node(r)
1428 1428 for s in obsolete.allsuccessors(repo.obsstore, [node]):
1429 1429 try:
1430 1430 allsuccessors[cl.rev(s)] = cl.rev(node)
1431 1431 except LookupError:
1432 1432 pass
1433 1433
1434 1434 if allsuccessors:
1435 1435 # Look for successors of obsolete nodes to be rebased among
1436 1436 # the ancestors of dest
1437 1437 ancs = cl.ancestors([repo[dest].rev()],
1438 1438 stoprev=min(allsuccessors),
1439 1439 inclusive=True)
1440 1440 for s in allsuccessors:
1441 1441 if s in ancs:
1442 1442 obsoletenotrebased[allsuccessors[s]] = s
1443 1443 elif (s == allsuccessors[s] and
1444 1444 allsuccessors.values().count(s) == 1):
1445 1445 # plain prune
1446 1446 obsoletenotrebased[s] = None
1447 1447
1448 1448 return obsoletenotrebased
1449 1449
1450 1450 def summaryhook(ui, repo):
1451 1451 if not repo.vfs.exists('rebasestate'):
1452 1452 return
1453 1453 try:
1454 1454 rbsrt = rebaseruntime(repo, ui, {})
1455 1455 rbsrt.restorestatus()
1456 1456 state = rbsrt.state
1457 1457 except error.RepoLookupError:
1458 1458 # i18n: column positioning for "hg summary"
1459 1459 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1460 1460 ui.write(msg)
1461 1461 return
1462 1462 numrebased = len([i for i in state.itervalues() if i >= 0])
1463 1463 # i18n: column positioning for "hg summary"
1464 1464 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1465 1465 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1466 1466 ui.label(_('%d remaining'), 'rebase.remaining') %
1467 1467 (len(state) - numrebased)))
1468 1468
1469 1469 def uisetup(ui):
1470 1470 #Replace pull with a decorator to provide --rebase option
1471 1471 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1472 1472 entry[1].append(('', 'rebase', None,
1473 1473 _("rebase working directory to branch head")))
1474 1474 entry[1].append(('t', 'tool', '',
1475 1475 _("specify merge tool for rebase")))
1476 1476 cmdutil.summaryhooks.add('rebase', summaryhook)
1477 1477 cmdutil.unfinishedstates.append(
1478 1478 ['rebasestate', False, False, _('rebase in progress'),
1479 1479 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1480 1480 cmdutil.afterresolvedstates.append(
1481 1481 ['rebasestate', _('hg rebase --continue')])
1482 1482 # ensure rebased rev are not hidden
1483 1483 extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
@@ -1,911 +1,915
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > rebase=
4 4 >
5 5 > [phases]
6 6 > publish=False
7 7 >
8 8 > [alias]
9 9 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
10 10 > EOF
11 11
12 12
13 13 $ hg init a
14 14 $ cd a
15 15 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
16 16 adding changesets
17 17 adding manifests
18 18 adding file changes
19 19 added 8 changesets with 7 changes to 7 files (+2 heads)
20 20 (run 'hg heads' to see heads, 'hg merge' to merge)
21 21 $ hg up tip
22 22 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
23 23 $ cd ..
24 24
25 25
26 26 Rebasing
27 27 D onto H - simple rebase:
28 28 (this also tests that editor is invoked if '--edit' is specified, and that we
29 29 can abort or warn for colliding untracked files)
30 30
31 31 $ hg clone -q -u . a a1
32 32 $ cd a1
33 33
34 34 $ hg tglog
35 35 @ 7: 'H'
36 36 |
37 37 | o 6: 'G'
38 38 |/|
39 39 o | 5: 'F'
40 40 | |
41 41 | o 4: 'E'
42 42 |/
43 43 | o 3: 'D'
44 44 | |
45 45 | o 2: 'C'
46 46 | |
47 47 | o 1: 'B'
48 48 |/
49 49 o 0: 'A'
50 50
51 51
52 52 $ hg status --rev "3^1" --rev 3
53 53 A D
54 54 $ echo collide > D
55 55 $ HGEDITOR=cat hg rebase -s 3 -d 7 --edit --config merge.checkunknown=warn
56 56 rebasing 3:32af7686d403 "D"
57 57 D: replacing untracked file
58 58 D
59 59
60 60
61 61 HG: Enter commit message. Lines beginning with 'HG:' are removed.
62 62 HG: Leave message empty to abort commit.
63 63 HG: --
64 64 HG: user: Nicolas Dumazet <nicdumz.commits@gmail.com>
65 65 HG: branch 'default'
66 66 HG: added D
67 67 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/32af7686d403-6f7dface-backup.hg (glob)
68 68 $ cat D.orig
69 69 collide
70 70 $ rm D.orig
71 71
72 72 $ hg tglog
73 73 o 7: 'D'
74 74 |
75 75 @ 6: 'H'
76 76 |
77 77 | o 5: 'G'
78 78 |/|
79 79 o | 4: 'F'
80 80 | |
81 81 | o 3: 'E'
82 82 |/
83 83 | o 2: 'C'
84 84 | |
85 85 | o 1: 'B'
86 86 |/
87 87 o 0: 'A'
88 88
89 89 $ cd ..
90 90
91 91
92 92 D onto F - intermediate point:
93 93 (this also tests that editor is not invoked if '--edit' is not specified, and
94 94 that we can ignore for colliding untracked files)
95 95
96 96 $ hg clone -q -u . a a2
97 97 $ cd a2
98 98 $ echo collide > D
99 99
100 100 $ HGEDITOR=cat hg rebase -s 3 -d 5 --config merge.checkunknown=ignore
101 101 rebasing 3:32af7686d403 "D"
102 102 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/32af7686d403-6f7dface-backup.hg (glob)
103 103 $ cat D.orig
104 104 collide
105 105 $ rm D.orig
106 106
107 107 $ hg tglog
108 108 o 7: 'D'
109 109 |
110 110 | @ 6: 'H'
111 111 |/
112 112 | o 5: 'G'
113 113 |/|
114 114 o | 4: 'F'
115 115 | |
116 116 | o 3: 'E'
117 117 |/
118 118 | o 2: 'C'
119 119 | |
120 120 | o 1: 'B'
121 121 |/
122 122 o 0: 'A'
123 123
124 124 $ cd ..
125 125
126 126
127 127 E onto H - skip of G:
128 128 (this also tests that we can overwrite untracked files and don't create backups
129 129 if they have the same contents)
130 130
131 131 $ hg clone -q -u . a a3
132 132 $ cd a3
133 133 $ hg cat -r 4 E | tee E
134 134 E
135 135
136 136 $ hg rebase -s 4 -d 7
137 137 rebasing 4:9520eea781bc "E"
138 138 rebasing 6:eea13746799a "G"
139 139 note: rebase of 6:eea13746799a created no changes to commit
140 140 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/9520eea781bc-fcd8edd4-backup.hg (glob)
141 141 $ f E.orig
142 142 E.orig: file not found
143 143
144 144 $ hg tglog
145 145 o 6: 'E'
146 146 |
147 147 @ 5: 'H'
148 148 |
149 149 o 4: 'F'
150 150 |
151 151 | o 3: 'D'
152 152 | |
153 153 | o 2: 'C'
154 154 | |
155 155 | o 1: 'B'
156 156 |/
157 157 o 0: 'A'
158 158
159 159 $ cd ..
160 160
161 161
162 162 F onto E - rebase of a branching point (skip G):
163 163
164 164 $ hg clone -q -u . a a4
165 165 $ cd a4
166 166
167 167 $ hg rebase -s 5 -d 4
168 168 rebasing 5:24b6387c8c8c "F"
169 169 rebasing 6:eea13746799a "G"
170 170 note: rebase of 6:eea13746799a created no changes to commit
171 171 rebasing 7:02de42196ebe "H" (tip)
172 172 saved backup bundle to $TESTTMP/a4/.hg/strip-backup/24b6387c8c8c-c3fe765d-backup.hg (glob)
173 173
174 174 $ hg tglog
175 175 @ 6: 'H'
176 176 |
177 177 o 5: 'F'
178 178 |
179 179 o 4: 'E'
180 180 |
181 181 | o 3: 'D'
182 182 | |
183 183 | o 2: 'C'
184 184 | |
185 185 | o 1: 'B'
186 186 |/
187 187 o 0: 'A'
188 188
189 189 $ cd ..
190 190
191 191
192 192 G onto H - merged revision having a parent in ancestors of target:
193 193
194 194 $ hg clone -q -u . a a5
195 195 $ cd a5
196 196
197 197 $ hg rebase -s 6 -d 7
198 198 rebasing 6:eea13746799a "G"
199 199 saved backup bundle to $TESTTMP/a5/.hg/strip-backup/eea13746799a-883828ed-backup.hg (glob)
200 200
201 201 $ hg tglog
202 202 o 7: 'G'
203 203 |\
204 204 | @ 6: 'H'
205 205 | |
206 206 | o 5: 'F'
207 207 | |
208 208 o | 4: 'E'
209 209 |/
210 210 | o 3: 'D'
211 211 | |
212 212 | o 2: 'C'
213 213 | |
214 214 | o 1: 'B'
215 215 |/
216 216 o 0: 'A'
217 217
218 218 $ cd ..
219 219
220 220
221 221 F onto B - G maintains E as parent:
222 222
223 223 $ hg clone -q -u . a a6
224 224 $ cd a6
225 225
226 226 $ hg rebase -s 5 -d 1
227 227 rebasing 5:24b6387c8c8c "F"
228 228 rebasing 6:eea13746799a "G"
229 229 rebasing 7:02de42196ebe "H" (tip)
230 230 saved backup bundle to $TESTTMP/a6/.hg/strip-backup/24b6387c8c8c-c3fe765d-backup.hg (glob)
231 231
232 232 $ hg tglog
233 233 @ 7: 'H'
234 234 |
235 235 | o 6: 'G'
236 236 |/|
237 237 o | 5: 'F'
238 238 | |
239 239 | o 4: 'E'
240 240 | |
241 241 | | o 3: 'D'
242 242 | | |
243 243 +---o 2: 'C'
244 244 | |
245 245 o | 1: 'B'
246 246 |/
247 247 o 0: 'A'
248 248
249 249 $ cd ..
250 250
251 251
252 252 These will fail (using --source):
253 253
254 254 G onto F - rebase onto an ancestor:
255 255
256 256 $ hg clone -q -u . a a7
257 257 $ cd a7
258 258
259 259 $ hg rebase -s 6 -d 5
260 260 nothing to rebase
261 261 [1]
262 262
263 263 F onto G - rebase onto a descendant:
264 264
265 265 $ hg rebase -s 5 -d 6
266 266 abort: source is ancestor of destination
267 267 [255]
268 268
269 269 G onto B - merge revision with both parents not in ancestors of target:
270 270
271 271 $ hg rebase -s 6 -d 1
272 272 rebasing 6:eea13746799a "G"
273 273 abort: cannot use revision 6 as base, result would have 3 parents
274 274 [255]
275 275 $ hg rebase --abort
276 276 rebase aborted
277 277
278 278 These will abort gracefully (using --base):
279 279
280 280 G onto G - rebase onto same changeset:
281 281
282 282 $ hg rebase -b 6 -d 6
283 283 nothing to rebase - eea13746799a is both "base" and destination
284 284 [1]
285 285
286 286 G onto F - rebase onto an ancestor:
287 287
288 288 $ hg rebase -b 6 -d 5
289 289 nothing to rebase
290 290 [1]
291 291
292 292 F onto G - rebase onto a descendant:
293 293
294 294 $ hg rebase -b 5 -d 6
295 295 nothing to rebase - "base" 24b6387c8c8c is already an ancestor of destination eea13746799a
296 296 [1]
297 297
298 298 C onto A - rebase onto an ancestor:
299 299
300 300 $ hg rebase -d 0 -s 2
301 301 rebasing 2:5fddd98957c8 "C"
302 302 rebasing 3:32af7686d403 "D"
303 303 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/5fddd98957c8-f9244fa1-backup.hg (glob)
304 304 $ hg tglog
305 305 o 7: 'D'
306 306 |
307 307 o 6: 'C'
308 308 |
309 309 | @ 5: 'H'
310 310 | |
311 311 | | o 4: 'G'
312 312 | |/|
313 313 | o | 3: 'F'
314 314 |/ /
315 315 | o 2: 'E'
316 316 |/
317 317 | o 1: 'B'
318 318 |/
319 319 o 0: 'A'
320 320
321 321
322 322 Check rebasing public changeset
323 323
324 324 $ hg pull --config phases.publish=True -q -r 6 . # update phase of 6
325 325 $ hg rebase -d 0 -b 6
326 326 nothing to rebase
327 327 [1]
328 328 $ hg rebase -d 5 -b 6
329 329 abort: can't rebase public changeset e1c4361dd923
330 330 (see 'hg help phases' for details)
331 331 [255]
332 $ hg rebase -d 5 -r '1 + (6::)'
333 abort: can't rebase public changeset e1c4361dd923
334 (see 'hg help phases' for details)
335 [255]
332 336
333 337 $ hg rebase -d 5 -b 6 --keep
334 338 rebasing 6:e1c4361dd923 "C"
335 339 rebasing 7:c9659aac0000 "D" (tip)
336 340
337 341 Check rebasing mutable changeset
338 342 Source phase greater or equal to destination phase: new changeset get the phase of source:
339 343 $ hg id -n
340 344 5
341 345 $ hg rebase -s9 -d0
342 346 rebasing 9:2b23e52411f4 "D" (tip)
343 347 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2b23e52411f4-f942decf-backup.hg (glob)
344 348 $ hg id -n # check we updated back to parent
345 349 5
346 350 $ hg log --template "{phase}\n" -r 9
347 351 draft
348 352 $ hg rebase -s9 -d1
349 353 rebasing 9:2cb10d0cfc6c "D" (tip)
350 354 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2cb10d0cfc6c-ddb0f256-backup.hg (glob)
351 355 $ hg log --template "{phase}\n" -r 9
352 356 draft
353 357 $ hg phase --force --secret 9
354 358 $ hg rebase -s9 -d0
355 359 rebasing 9:c5b12b67163a "D" (tip)
356 360 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/c5b12b67163a-4e372053-backup.hg (glob)
357 361 $ hg log --template "{phase}\n" -r 9
358 362 secret
359 363 $ hg rebase -s9 -d1
360 364 rebasing 9:2a0524f868ac "D" (tip)
361 365 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2a0524f868ac-cefd8574-backup.hg (glob)
362 366 $ hg log --template "{phase}\n" -r 9
363 367 secret
364 368 Source phase lower than destination phase: new changeset get the phase of destination:
365 369 $ hg rebase -s8 -d9
366 370 rebasing 8:6d4f22462821 "C"
367 371 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/6d4f22462821-3441f70b-backup.hg (glob)
368 372 $ hg log --template "{phase}\n" -r 'rev(9)'
369 373 secret
370 374
371 375 $ cd ..
372 376
373 377 Test for revset
374 378
375 379 We need a bit different graph
376 380 All destination are B
377 381
378 382 $ hg init ah
379 383 $ cd ah
380 384 $ hg unbundle "$TESTDIR/bundles/rebase-revset.hg"
381 385 adding changesets
382 386 adding manifests
383 387 adding file changes
384 388 added 9 changesets with 9 changes to 9 files (+2 heads)
385 389 (run 'hg heads' to see heads, 'hg merge' to merge)
386 390 $ hg tglog
387 391 o 8: 'I'
388 392 |
389 393 o 7: 'H'
390 394 |
391 395 o 6: 'G'
392 396 |
393 397 | o 5: 'F'
394 398 | |
395 399 | o 4: 'E'
396 400 |/
397 401 o 3: 'D'
398 402 |
399 403 o 2: 'C'
400 404 |
401 405 | o 1: 'B'
402 406 |/
403 407 o 0: 'A'
404 408
405 409 $ cd ..
406 410
407 411
408 412 Simple case with keep:
409 413
410 414 Source on have two descendant heads but ask for one
411 415
412 416 $ hg clone -q -u . ah ah1
413 417 $ cd ah1
414 418 $ hg rebase -r '2::8' -d 1
415 419 abort: can't remove original changesets with unrebased descendants
416 420 (use --keep to keep original changesets)
417 421 [255]
418 422 $ hg rebase -r '2::8' -d 1 -k
419 423 rebasing 2:c9e50f6cdc55 "C"
420 424 rebasing 3:ffd453c31098 "D"
421 425 rebasing 6:3d8a618087a7 "G"
422 426 rebasing 7:72434a4e60b0 "H"
423 427 rebasing 8:479ddb54a924 "I" (tip)
424 428 $ hg tglog
425 429 o 13: 'I'
426 430 |
427 431 o 12: 'H'
428 432 |
429 433 o 11: 'G'
430 434 |
431 435 o 10: 'D'
432 436 |
433 437 o 9: 'C'
434 438 |
435 439 | o 8: 'I'
436 440 | |
437 441 | o 7: 'H'
438 442 | |
439 443 | o 6: 'G'
440 444 | |
441 445 | | o 5: 'F'
442 446 | | |
443 447 | | o 4: 'E'
444 448 | |/
445 449 | o 3: 'D'
446 450 | |
447 451 | o 2: 'C'
448 452 | |
449 453 o | 1: 'B'
450 454 |/
451 455 o 0: 'A'
452 456
453 457
454 458 $ cd ..
455 459
456 460 Base on have one descendant heads we ask for but common ancestor have two
457 461
458 462 $ hg clone -q -u . ah ah2
459 463 $ cd ah2
460 464 $ hg rebase -r '3::8' -d 1
461 465 abort: can't remove original changesets with unrebased descendants
462 466 (use --keep to keep original changesets)
463 467 [255]
464 468 $ hg rebase -r '3::8' -d 1 --keep
465 469 rebasing 3:ffd453c31098 "D"
466 470 rebasing 6:3d8a618087a7 "G"
467 471 rebasing 7:72434a4e60b0 "H"
468 472 rebasing 8:479ddb54a924 "I" (tip)
469 473 $ hg tglog
470 474 o 12: 'I'
471 475 |
472 476 o 11: 'H'
473 477 |
474 478 o 10: 'G'
475 479 |
476 480 o 9: 'D'
477 481 |
478 482 | o 8: 'I'
479 483 | |
480 484 | o 7: 'H'
481 485 | |
482 486 | o 6: 'G'
483 487 | |
484 488 | | o 5: 'F'
485 489 | | |
486 490 | | o 4: 'E'
487 491 | |/
488 492 | o 3: 'D'
489 493 | |
490 494 | o 2: 'C'
491 495 | |
492 496 o | 1: 'B'
493 497 |/
494 498 o 0: 'A'
495 499
496 500
497 501 $ cd ..
498 502
499 503 rebase subset
500 504
501 505 $ hg clone -q -u . ah ah3
502 506 $ cd ah3
503 507 $ hg rebase -r '3::7' -d 1
504 508 abort: can't remove original changesets with unrebased descendants
505 509 (use --keep to keep original changesets)
506 510 [255]
507 511 $ hg rebase -r '3::7' -d 1 --keep
508 512 rebasing 3:ffd453c31098 "D"
509 513 rebasing 6:3d8a618087a7 "G"
510 514 rebasing 7:72434a4e60b0 "H"
511 515 $ hg tglog
512 516 o 11: 'H'
513 517 |
514 518 o 10: 'G'
515 519 |
516 520 o 9: 'D'
517 521 |
518 522 | o 8: 'I'
519 523 | |
520 524 | o 7: 'H'
521 525 | |
522 526 | o 6: 'G'
523 527 | |
524 528 | | o 5: 'F'
525 529 | | |
526 530 | | o 4: 'E'
527 531 | |/
528 532 | o 3: 'D'
529 533 | |
530 534 | o 2: 'C'
531 535 | |
532 536 o | 1: 'B'
533 537 |/
534 538 o 0: 'A'
535 539
536 540
537 541 $ cd ..
538 542
539 543 rebase subset with multiple head
540 544
541 545 $ hg clone -q -u . ah ah4
542 546 $ cd ah4
543 547 $ hg rebase -r '3::(7+5)' -d 1
544 548 abort: can't remove original changesets with unrebased descendants
545 549 (use --keep to keep original changesets)
546 550 [255]
547 551 $ hg rebase -r '3::(7+5)' -d 1 --keep
548 552 rebasing 3:ffd453c31098 "D"
549 553 rebasing 4:c01897464e7f "E"
550 554 rebasing 5:41bfcc75ed73 "F"
551 555 rebasing 6:3d8a618087a7 "G"
552 556 rebasing 7:72434a4e60b0 "H"
553 557 $ hg tglog
554 558 o 13: 'H'
555 559 |
556 560 o 12: 'G'
557 561 |
558 562 | o 11: 'F'
559 563 | |
560 564 | o 10: 'E'
561 565 |/
562 566 o 9: 'D'
563 567 |
564 568 | o 8: 'I'
565 569 | |
566 570 | o 7: 'H'
567 571 | |
568 572 | o 6: 'G'
569 573 | |
570 574 | | o 5: 'F'
571 575 | | |
572 576 | | o 4: 'E'
573 577 | |/
574 578 | o 3: 'D'
575 579 | |
576 580 | o 2: 'C'
577 581 | |
578 582 o | 1: 'B'
579 583 |/
580 584 o 0: 'A'
581 585
582 586
583 587 $ cd ..
584 588
585 589 More advanced tests
586 590
587 591 rebase on ancestor with revset
588 592
589 593 $ hg clone -q -u . ah ah5
590 594 $ cd ah5
591 595 $ hg rebase -r '6::' -d 2
592 596 rebasing 6:3d8a618087a7 "G"
593 597 rebasing 7:72434a4e60b0 "H"
594 598 rebasing 8:479ddb54a924 "I" (tip)
595 599 saved backup bundle to $TESTTMP/ah5/.hg/strip-backup/3d8a618087a7-b4f73f31-backup.hg (glob)
596 600 $ hg tglog
597 601 o 8: 'I'
598 602 |
599 603 o 7: 'H'
600 604 |
601 605 o 6: 'G'
602 606 |
603 607 | o 5: 'F'
604 608 | |
605 609 | o 4: 'E'
606 610 | |
607 611 | o 3: 'D'
608 612 |/
609 613 o 2: 'C'
610 614 |
611 615 | o 1: 'B'
612 616 |/
613 617 o 0: 'A'
614 618
615 619 $ cd ..
616 620
617 621
618 622 rebase with multiple root.
619 623 We rebase E and G on B
620 624 We would expect heads are I, F if it was supported
621 625
622 626 $ hg clone -q -u . ah ah6
623 627 $ cd ah6
624 628 $ hg rebase -r '(4+6)::' -d 1
625 629 rebasing 4:c01897464e7f "E"
626 630 rebasing 5:41bfcc75ed73 "F"
627 631 rebasing 6:3d8a618087a7 "G"
628 632 rebasing 7:72434a4e60b0 "H"
629 633 rebasing 8:479ddb54a924 "I" (tip)
630 634 saved backup bundle to $TESTTMP/ah6/.hg/strip-backup/3d8a618087a7-aae93a24-backup.hg (glob)
631 635 $ hg tglog
632 636 o 8: 'I'
633 637 |
634 638 o 7: 'H'
635 639 |
636 640 o 6: 'G'
637 641 |
638 642 | o 5: 'F'
639 643 | |
640 644 | o 4: 'E'
641 645 |/
642 646 | o 3: 'D'
643 647 | |
644 648 | o 2: 'C'
645 649 | |
646 650 o | 1: 'B'
647 651 |/
648 652 o 0: 'A'
649 653
650 654 $ cd ..
651 655
652 656 More complex rebase with multiple roots
653 657 each root have a different common ancestor with the destination and this is a detach
654 658
655 659 (setup)
656 660
657 661 $ hg clone -q -u . a a8
658 662 $ cd a8
659 663 $ echo I > I
660 664 $ hg add I
661 665 $ hg commit -m I
662 666 $ hg up 4
663 667 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
664 668 $ echo I > J
665 669 $ hg add J
666 670 $ hg commit -m J
667 671 created new head
668 672 $ echo I > K
669 673 $ hg add K
670 674 $ hg commit -m K
671 675 $ hg tglog
672 676 @ 10: 'K'
673 677 |
674 678 o 9: 'J'
675 679 |
676 680 | o 8: 'I'
677 681 | |
678 682 | o 7: 'H'
679 683 | |
680 684 +---o 6: 'G'
681 685 | |/
682 686 | o 5: 'F'
683 687 | |
684 688 o | 4: 'E'
685 689 |/
686 690 | o 3: 'D'
687 691 | |
688 692 | o 2: 'C'
689 693 | |
690 694 | o 1: 'B'
691 695 |/
692 696 o 0: 'A'
693 697
694 698 (actual test)
695 699
696 700 $ hg rebase --dest 'desc(G)' --rev 'desc(K) + desc(I)'
697 701 rebasing 8:e7ec4e813ba6 "I"
698 702 rebasing 10:23a4ace37988 "K" (tip)
699 703 saved backup bundle to $TESTTMP/a8/.hg/strip-backup/23a4ace37988-b06984b3-backup.hg (glob)
700 704 $ hg log --rev 'children(desc(G))'
701 705 changeset: 9:adb617877056
702 706 parent: 6:eea13746799a
703 707 user: test
704 708 date: Thu Jan 01 00:00:00 1970 +0000
705 709 summary: I
706 710
707 711 changeset: 10:882431a34a0e
708 712 tag: tip
709 713 parent: 6:eea13746799a
710 714 user: test
711 715 date: Thu Jan 01 00:00:00 1970 +0000
712 716 summary: K
713 717
714 718 $ hg tglog
715 719 @ 10: 'K'
716 720 |
717 721 | o 9: 'I'
718 722 |/
719 723 | o 8: 'J'
720 724 | |
721 725 | | o 7: 'H'
722 726 | | |
723 727 o---+ 6: 'G'
724 728 |/ /
725 729 | o 5: 'F'
726 730 | |
727 731 o | 4: 'E'
728 732 |/
729 733 | o 3: 'D'
730 734 | |
731 735 | o 2: 'C'
732 736 | |
733 737 | o 1: 'B'
734 738 |/
735 739 o 0: 'A'
736 740
737 741
738 742 Test that rebase is not confused by $CWD disappearing during rebase (issue4121)
739 743
740 744 $ cd ..
741 745 $ hg init cwd-vanish
742 746 $ cd cwd-vanish
743 747 $ touch initial-file
744 748 $ hg add initial-file
745 749 $ hg commit -m 'initial commit'
746 750 $ touch dest-file
747 751 $ hg add dest-file
748 752 $ hg commit -m 'dest commit'
749 753 $ hg up 0
750 754 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
751 755 $ touch other-file
752 756 $ hg add other-file
753 757 $ hg commit -m 'first source commit'
754 758 created new head
755 759 $ mkdir subdir
756 760 $ cd subdir
757 761 $ touch subfile
758 762 $ hg add subfile
759 763 $ hg commit -m 'second source with subdir'
760 764 #if rmcwd
761 765 $ hg rebase -b . -d 1 --traceback
762 766 rebasing 2:779a07b1b7a0 "first source commit"
763 767 current directory was removed
764 768 (consider changing to repo root: $TESTTMP/cwd-vanish)
765 769 rebasing 3:a7d6f3a00bf3 "second source with subdir" (tip)
766 770 saved backup bundle to $TESTTMP/cwd-vanish/.hg/strip-backup/779a07b1b7a0-853e0073-backup.hg (glob)
767 771 #else
768 772 $ hg rebase -b . -d 1 --traceback
769 773 rebasing 2:779a07b1b7a0 "first source commit"
770 774 rebasing 3:a7d6f3a00bf3 "second source with subdir" (tip)
771 775 saved backup bundle to $TESTTMP/cwd-vanish/.hg/strip-backup/779a07b1b7a0-853e0073-backup.hg (glob)
772 776 #endif
773 777
774 778 Get back to the root of cwd-vanish. Note that even though `cd ..`
775 779 works on most systems, it does not work on FreeBSD 10, so we use an
776 780 absolute path to get back to the repository.
777 781 $ cd $TESTTMP
778 782
779 783 Test that rebase is done in topo order (issue5370)
780 784
781 785 $ hg init order
782 786 $ cd order
783 787 $ touch a && hg add a && hg ci -m A
784 788 $ touch b && hg add b && hg ci -m B
785 789 $ touch c && hg add c && hg ci -m C
786 790 $ hg up 1
787 791 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
788 792 $ touch d && hg add d && hg ci -m D
789 793 created new head
790 794 $ hg up 2
791 795 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
792 796 $ touch e && hg add e && hg ci -m E
793 797 $ hg up 3
794 798 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
795 799 $ touch f && hg add f && hg ci -m F
796 800 $ hg up 0
797 801 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
798 802 $ touch g && hg add g && hg ci -m G
799 803 created new head
800 804
801 805 $ hg tglog
802 806 @ 6: 'G'
803 807 |
804 808 | o 5: 'F'
805 809 | |
806 810 | | o 4: 'E'
807 811 | | |
808 812 | o | 3: 'D'
809 813 | | |
810 814 | | o 2: 'C'
811 815 | |/
812 816 | o 1: 'B'
813 817 |/
814 818 o 0: 'A'
815 819
816 820
817 821 $ hg rebase -s 1 -d 6
818 822 rebasing 1:76035bbd54bd "B"
819 823 rebasing 2:d84f5cfaaf14 "C"
820 824 rebasing 4:82ae8dc7a9b7 "E"
821 825 rebasing 3:ab709c9f7171 "D"
822 826 rebasing 5:412b391de760 "F"
823 827 saved backup bundle to $TESTTMP/order/.hg/strip-backup/76035bbd54bd-e341bc99-backup.hg (glob)
824 828
825 829 $ hg tglog
826 830 o 6: 'F'
827 831 |
828 832 o 5: 'D'
829 833 |
830 834 | o 4: 'E'
831 835 | |
832 836 | o 3: 'C'
833 837 |/
834 838 o 2: 'B'
835 839 |
836 840 @ 1: 'G'
837 841 |
838 842 o 0: 'A'
839 843
840 844
841 845 Test experimental revset
842 846 ========================
843 847
844 848 $ cd ../cwd-vanish
845 849
846 850 Make the repo a bit more interesting
847 851
848 852 $ hg up 1
849 853 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
850 854 $ echo aaa > aaa
851 855 $ hg add aaa
852 856 $ hg commit -m aaa
853 857 created new head
854 858 $ hg log -G
855 859 @ changeset: 4:5f7bc9025ed2
856 860 | tag: tip
857 861 | parent: 1:58d79cc1cf43
858 862 | user: test
859 863 | date: Thu Jan 01 00:00:00 1970 +0000
860 864 | summary: aaa
861 865 |
862 866 | o changeset: 3:1910d5ff34ea
863 867 | | user: test
864 868 | | date: Thu Jan 01 00:00:00 1970 +0000
865 869 | | summary: second source with subdir
866 870 | |
867 871 | o changeset: 2:82901330b6ef
868 872 |/ user: test
869 873 | date: Thu Jan 01 00:00:00 1970 +0000
870 874 | summary: first source commit
871 875 |
872 876 o changeset: 1:58d79cc1cf43
873 877 | user: test
874 878 | date: Thu Jan 01 00:00:00 1970 +0000
875 879 | summary: dest commit
876 880 |
877 881 o changeset: 0:e94b687f7da3
878 882 user: test
879 883 date: Thu Jan 01 00:00:00 1970 +0000
880 884 summary: initial commit
881 885
882 886
883 887 Testing from lower head
884 888
885 889 $ hg up 3
886 890 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
887 891 $ hg log -r '_destrebase()'
888 892 changeset: 4:5f7bc9025ed2
889 893 tag: tip
890 894 parent: 1:58d79cc1cf43
891 895 user: test
892 896 date: Thu Jan 01 00:00:00 1970 +0000
893 897 summary: aaa
894 898
895 899
896 900 Testing from upper head
897 901
898 902 $ hg log -r '_destrebase(4)'
899 903 changeset: 3:1910d5ff34ea
900 904 user: test
901 905 date: Thu Jan 01 00:00:00 1970 +0000
902 906 summary: second source with subdir
903 907
904 908 $ hg up 4
905 909 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
906 910 $ hg log -r '_destrebase()'
907 911 changeset: 3:1910d5ff34ea
908 912 user: test
909 913 date: Thu Jan 01 00:00:00 1970 +0000
910 914 summary: second source with subdir
911 915
General Comments 0
You need to be logged in to leave comments. Login now