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